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::{AssertSqlSafe, 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 continue_subscriptions(
399 &mut self,
400 previous: Option<&GraphQLState>,
401 ) -> Result<(), crate::subscription::BrokerError> {
402 if let Some(previous) = previous {
403 self.broker = Arc::clone(&previous.broker);
404 }
405 if !self.config.enable_subscriptions {
406 return Ok(());
407 }
408 if let Some(broker) = self.broker.read().await.as_ref() {
409 let channels = self
410 .subscription_fields
411 .iter()
412 .map(|f| f.channel_name())
413 .collect();
414 return broker.listen(channels).await;
415 }
416 self.init_subscriptions().await
417 }
418
419 pub async fn get_broker(&self) -> Option<Arc<RwLock<Option<NotifyBroker>>>> {
421 Some(Arc::clone(&self.broker))
422 }
423}
424
425pub async fn graphql_handler(
427 State(state): State<Arc<GraphQLState>>,
428 ctx: GraphQLContext,
429 req: GraphQLRequest,
430) -> GraphQLResponse {
431 let request = req
432 .into_inner()
433 .data(ctx)
434 .data(state.pool.clone())
435 .data(Arc::clone(&state.broker));
436 state.schema.execute(request).await.into()
437}
438
439pub async fn graphql_ws_handler(
450 State(state): State<Arc<GraphQLState>>,
451 protocol: async_graphql_axum::GraphQLProtocol,
452 ws: axum::extract::WebSocketUpgrade,
453) -> impl IntoResponse {
454 let schema = state.schema.clone();
455 let pool = state.pool.clone();
456 let broker = Arc::clone(&state.broker);
457
458 ws.protocols(["graphql-transport-ws", "graphql-ws"])
459 .on_upgrade(move |socket| async move {
460 let mut data = async_graphql::Data::default();
461 data.insert(pool);
462 data.insert(broker);
463
464 async_graphql_axum::GraphQLWebSocket::new(socket, schema, protocol)
465 .with_data(data)
466 .serve()
467 .await
468 })
469}
470
471pub async fn graphql_playground() -> impl axum::response::IntoResponse {
473 axum::response::Html(async_graphql::http::playground_source(
474 async_graphql::http::GraphQLPlaygroundConfig::new("/graphql")
475 .subscription_endpoint("/graphql/ws"),
476 ))
477}
478
479fn column_for<'a>(
486 names: &'a crate::names::NameOverrides,
487 schema: &str,
488 table: &str,
489 field: &'a str,
490) -> &'a str {
491 names.column_source(schema, table, field).unwrap_or(field)
492}
493
494fn table_column_for<'a>(
496 names: &'a crate::names::NameOverrides,
497 table: &postrust_core::schema_cache::Table,
498 field: &'a str,
499) -> &'a str {
500 column_for(names, &table.schema, &table.name, field)
501}
502
503fn rename_projection(
511 table: &postrust_core::schema_cache::Table,
512 alias: &str,
513 names: &crate::names::NameOverrides,
514) -> Option<String> {
515 if !names.renames_columns(&table.schema, &table.name) {
516 return None;
517 }
518 let parts: Vec<String> = table
519 .columns
520 .values()
521 .map(|column| {
522 let exposed = names
523 .column(&table.schema, &table.name, &column.name)
524 .unwrap_or(&column.name);
525 format!(
526 "{}.{} AS {}",
527 postrust_sql::escape_ident(alias),
528 postrust_sql::escape_ident(&column.name),
529 postrust_sql::escape_ident(exposed)
530 )
531 })
532 .collect();
533 match parts.is_empty() {
534 true => None,
535 false => Some(parts.join(", ")),
536 }
537}
538
539fn exposed_column_types(
545 table: &postrust_core::schema_cache::Table,
546 names: &crate::names::NameOverrides,
547) -> HashMap<String, String> {
548 table
549 .columns
550 .values()
551 .map(|column| {
552 let exposed = names
553 .column(&table.schema, &table.name, &column.name)
554 .unwrap_or(&column.name);
555 (exposed.to_string(), column.nominal_type.clone())
556 })
557 .collect()
558}
559
560fn write_type_ref(graphql_type: &crate::types::GraphQLType) -> TypeRef {
566 match graphql_type {
567 crate::types::GraphQLType::List(inner) => TypeRef::named_list(leaf_scalar_name(inner)),
568 other => TypeRef::named(leaf_scalar_name(other)),
569 }
570}
571
572fn leaf_scalar_name(graphql_type: &crate::types::GraphQLType) -> String {
574 match graphql_type {
575 crate::types::GraphQLType::List(inner) => leaf_scalar_name(inner),
576 other => other.to_string(),
577 }
578}
579
580fn build_dynamic_schema(
582 generated: &GeneratedSchema,
583 _schema_cache: &SchemaCache,
584 subscription_fields: Option<&[SubField]>,
585 max_rows: Option<i64>,
586 names: Arc<crate::names::NameOverrides>,
587 subscription_refresh: std::time::Duration,
588 role: Option<&str>,
593) -> Result<Schema, GraphQLError> {
594 let mut object_types: HashMap<String, Object> = HashMap::new();
596
597 for (type_name, obj) in &generated.object_types {
602 if obj.fields.is_empty() {
603 continue;
604 }
605 let relationships = generated
606 .relationship_fields
607 .get(type_name)
608 .map(|r| r.as_slice())
609 .unwrap_or(&[]);
610 let table_obj = create_object_type(
611 obj,
612 relationships,
613 generated.federation_entities.get(type_name),
614 );
615 object_types.insert(type_name.clone(), table_obj);
616 }
617
618 let countable: HashSet<&str> = generated
624 .query_fields
625 .iter()
626 .filter(|field| field.aggregates && !field.is_by_pk)
627 .map(|field| field.type_name.as_str())
628 .collect();
629 for (type_name, obj) in &generated.object_types {
630 if obj.fields.is_empty() && !countable.contains(type_name.as_str()) {
631 continue;
632 }
633 for aggregate_type in create_aggregate_types(&obj.local_name, obj) {
634 object_types.insert(aggregate_type.type_name().to_string(), aggregate_type);
635 }
636 }
637
638 let mutable: HashMap<String, String> = generated
642 .mutation_fields
643 .iter()
644 .filter_map(|f| {
645 let response = f
653 .return_type
654 .trim_matches(|c| c == '[' || c == ']' || c == '!')
655 .strip_suffix("_mutation_response")?;
656 Some((response.to_string(), f.type_name.clone()))
657 })
658 .collect();
659 for (base_name, row_type_name) in mutable {
660 let returning = generated
664 .object_types
665 .get(&row_type_name)
666 .is_some_and(|object| !object.fields.is_empty());
667 object_types.insert(
668 mutation_response_type_name(&base_name),
669 create_mutation_response_type(&base_name, &row_type_name, returning),
670 );
671 }
672
673 let query_root = if generated.enable_federation {
678 "Query"
679 } else {
680 "query_root"
681 };
682 let mutation_root = if generated.enable_federation {
683 "Mutation"
684 } else {
685 "mutation_root"
686 };
687 let subscription_root = if generated.enable_federation {
688 "Subscription"
689 } else {
690 "subscription_root"
691 };
692
693 let relationships = Arc::new(generated.relationship_fields.clone());
696 let type_names: Arc<HashMap<(String, String), String>> = Arc::new(
702 generated
703 .object_types
704 .iter()
705 .map(|(type_name, object)| {
706 (
707 (object.table.schema.clone(), object.table.name.clone()),
708 type_name.clone(),
709 )
710 })
711 .collect(),
712 );
713 let query = add_function_fields(
714 create_query_type(
715 generated,
716 query_root,
717 max_rows,
718 Arc::clone(&relationships),
719 Arc::clone(&names),
720 ),
721 generated,
722 false,
723 max_rows,
724 Arc::clone(&relationships),
725 Arc::clone(&names),
726 );
727
728 let mutation = if !generated.mutation_fields.is_empty()
730 || generated.function_fields.iter().any(|f| f.volatile)
731 {
732 Some(add_function_fields(
733 create_mutation_type(
734 generated,
735 mutation_root,
736 Arc::clone(&relationships),
737 Arc::clone(&type_names),
738 Arc::clone(&names),
739 max_rows,
740 role,
741 ),
742 generated,
743 true,
744 max_rows,
745 Arc::clone(&relationships),
746 Arc::clone(&names),
747 ))
748 } else {
749 None
750 };
751
752 let subscription = subscription_fields.map(|_| {
754 create_subscription_type(
755 generated,
756 subscription_root,
757 max_rows,
758 Arc::clone(&relationships),
759 Arc::clone(&names),
760 subscription_refresh,
761 )
762 });
763
764 let mut builder = Schema::build(
766 query_root,
767 mutation.as_ref().map(|_| mutation_root),
768 subscription.as_ref().map(|_| subscription_root),
769 );
770 if generated.enable_federation {
771 builder = builder
772 .enable_federation()
773 .extension(NullableFederationEntity);
774 let entity_state = Arc::new(EntityResolverState::new(
775 generated,
776 Arc::clone(&relationships),
777 Arc::clone(&names),
778 max_rows,
779 ));
780 builder = builder.entity_resolver(move |ctx| {
781 let state = Arc::clone(&entity_state);
782 FieldFuture::new(async move { resolve_entities(&ctx, state.as_ref()).await })
783 });
784 }
785
786 for (_, obj) in object_types {
788 builder = builder.register(obj);
789 }
790
791 builder = builder.register(query);
793
794 if let Some(mutation) = mutation {
796 builder = builder.register(mutation);
797 }
798
799 if let Some(subscription) = subscription {
801 builder = builder.register(subscription);
802 }
803
804 let (bool_exp_inputs, bool_exp_scalars) = crate::input::bool_exp::build_inputs(
811 &generated.object_types,
812 &generated.relationship_fields,
813 );
814 for input in bool_exp_inputs {
815 builder = builder.register(input);
816 }
817
818 let mut scalar_names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
819 for object in generated.object_types.values() {
820 for field in object.fields.iter().chain(&object.writable_fields) {
826 scalar_names.insert(leaf_scalar_name(&field.graphql_type));
827 }
828 }
829 scalar_names.insert("JSON".to_string());
832 for function in &generated.function_fields {
835 for (_, pg_type, _) in &function.arguments {
836 scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
837 }
838 }
839 for object in generated.object_types.values() {
842 for field in &object.fields {
843 for (_, pg_type, _) in &field.arguments {
844 scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
845 }
846 }
847 }
848 for relationships in generated.relationship_fields.values() {
849 for relationship in relationships {
850 for (_, pg_type, _) in &relationship.arguments {
851 scalar_names.insert(leaf_scalar_name(&crate::types::pg_type_to_graphql(pg_type)));
852 }
853 }
854 }
855
856 scalar_names.extend(bool_exp_scalars);
862
863 for name in scalar_names {
864 if matches!(name.as_str(), "Int" | "Float" | "String" | "Boolean" | "ID") {
865 continue;
866 }
867 if generated.enum_types.contains_key(&name) {
871 continue;
872 }
873 builder = builder
874 .register(Scalar::new(&name).description(format!("The PostgreSQL `{}` type.", name)));
875 }
876
877 let mut key_inputs: HashMap<String, InputObject> = HashMap::new();
885 for field in &generated.mutation_fields {
886 if field.mutation_type != MutationType::UpdateByPk || field.pk_columns.is_empty() {
887 continue;
888 }
889 let base_name = field.local_type_name.as_str();
890 let type_name = format!("{}_pk_columns_input", base_name);
891 let mut input = InputObject::new(&type_name)
892 .description(format!("The primary key of one {} row.", base_name));
893 for (column, pg_type) in &field.pk_columns {
894 input = input.field(InputValue::new(
895 names
896 .column(&field.schema_name, &field.table_name, column)
897 .unwrap_or(column),
898 TypeRef::named_nn(exposed_pk_argument_type(
899 names.as_ref(),
900 &field.schema_name,
901 &field.table_name,
902 column,
903 pg_type,
904 )),
905 ));
906 }
907 key_inputs.insert(type_name, input);
908 }
909 for (_, input) in key_inputs {
910 builder = builder.register(input);
911 }
912
913 let insertable_types: HashSet<&str> = generated
934 .object_types
935 .iter()
936 .filter(|(_, object)| crate::input::mutation::is_insertable(&object.table))
937 .map(|(type_name, _)| type_name.as_str())
938 .collect();
939
940 for (type_name, object) in &generated.object_types {
941 let local_type_name = object.local_name.as_str();
942 let table = &object.table;
943 let writable_named = |allowed: Option<&crate::names::ColumnSet>| {
951 object
952 .writable_fields
953 .iter()
954 .filter(|field| {
955 let column = table_column_for(&names, table, &field.name);
956 table.get_column(column).is_some()
957 && allowed.is_none_or(|set| set.allows(column))
958 })
959 .collect::<Vec<&crate::schema::object::GraphQLField>>()
960 };
961 let writable = writable_named(None);
962 let insert_fields = write_fields(object, &names, role, crate::role::Verb::Insert);
963 let update_fields = write_fields(object, &names, role, crate::role::Verb::Update);
964 if writable.is_empty() {
965 continue;
966 }
967
968 let relationships = generated
969 .relationship_fields
970 .get(type_name)
971 .map(|r| r.as_slice())
972 .unwrap_or(&[]);
973 let conflict_type = match table.unique_constraints.is_empty() {
974 true => None,
975 false => Some(format!("{}_on_conflict", local_type_name)),
976 };
977
978 if crate::input::mutation::is_insertable(table) {
979 let mut insert = InputObject::new(format!("{}_insert_input", local_type_name))
980 .description(format!("The columns of a new {} row.", local_type_name));
981 let mut taken: HashSet<&str> = HashSet::new();
982 for field in &insert_fields {
983 taken.insert(field.name.as_str());
987 insert = insert.field(InputValue::new(
988 &field.name,
989 write_type_ref(&field.graphql_type),
990 ));
991 }
992 for relationship in relationships {
996 if !insertable_types.contains(relationship.target_type.as_str()) {
997 continue;
998 }
999 if !taken.insert(relationship.name.as_str()) {
1000 continue;
1001 }
1002 let suffix = match relationship.is_list {
1003 true => "arr_rel_insert_input",
1004 false => "obj_rel_insert_input",
1005 };
1006 insert = insert.field(InputValue::new(
1007 &relationship.name,
1008 TypeRef::named(format!("{}_{}", relationship.target_local_name, suffix)),
1009 ));
1010 }
1011 builder = builder.register(insert);
1012
1013 let data_type = format!("{}_insert_input", local_type_name);
1017 let mut object_rel =
1018 InputObject::new(format!("{}_obj_rel_insert_input", local_type_name))
1019 .description(format!(
1020 "One {} row written beside its parent.",
1021 local_type_name
1022 ))
1023 .field(InputValue::new("data", TypeRef::named_nn(&data_type)));
1024 let mut array_rel =
1025 InputObject::new(format!("{}_arr_rel_insert_input", local_type_name))
1026 .description(format!(
1027 "{} rows written beside their parent.",
1028 local_type_name
1029 ))
1030 .field(InputValue::new(
1031 "data",
1032 TypeRef::named_nn_list_nn(&data_type),
1033 ));
1034 if let Some(conflict) = &conflict_type {
1035 object_rel =
1036 object_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
1037 array_rel =
1038 array_rel.field(InputValue::new("on_conflict", TypeRef::named(conflict)));
1039 }
1040 builder = builder.register(object_rel).register(array_rel);
1041 }
1042
1043 if crate::input::mutation::is_updatable(table) {
1044 let mut set = InputObject::new(format!("{}_set_input", local_type_name))
1045 .description(format!("Columns of {} to replace.", local_type_name));
1046 let mut numeric = InputObject::new(format!("{}_inc_input", local_type_name))
1047 .description(format!("Columns of {} to add to.", local_type_name));
1048 let mut any_numeric = false;
1049 let mut any_jsonb = false;
1050 for field in &update_fields {
1051 if matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
1052 any_jsonb = true;
1053 }
1054 set = set.field(InputValue::new(
1055 &field.name,
1056 write_type_ref(&field.graphql_type),
1057 ));
1058 if crate::schema::aggregate::is_incrementable(&field.graphql_type) {
1061 any_numeric = true;
1062 numeric = numeric.field(InputValue::new(
1063 &field.name,
1064 TypeRef::named(leaf_scalar_name(&field.graphql_type)),
1065 ));
1066 }
1067 }
1068 builder = builder.register(set);
1069 if any_numeric {
1071 builder = builder.register(numeric);
1072 }
1073
1074 let mut updates = InputObject::new(format!("{}_updates", local_type_name))
1079 .description(format!(
1080 "One update to {}: which rows, and what to write.",
1081 local_type_name
1082 ))
1083 .field(InputValue::new(
1084 "where",
1085 TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(local_type_name)),
1086 ))
1087 .field(InputValue::new(
1088 "_set",
1089 TypeRef::named(format!("{}_set_input", local_type_name)),
1090 ));
1091 if any_numeric {
1092 updates = updates.field(InputValue::new(
1093 "_inc",
1094 TypeRef::named(format!("{}_inc_input", local_type_name)),
1095 ));
1096 }
1097 for (operator, _) in JSONB_OPERATORS {
1104 if !any_jsonb {
1105 break;
1106 }
1107 updates = updates.field(InputValue::new(
1108 *operator,
1109 TypeRef::named(jsonb_operator_input(local_type_name, operator)),
1110 ));
1111 }
1112 builder = builder.register(updates);
1113 if any_jsonb {
1114 for (operator, item) in JSONB_OPERATORS {
1115 let mut input =
1116 InputObject::new(jsonb_operator_input(local_type_name, operator))
1117 .description(format!(
1118 "Columns of {} to apply `{}` to.",
1119 local_type_name, operator
1120 ));
1121 for field in &update_fields {
1122 if !matches!(&field.graphql_type, crate::types::GraphQLType::Json) {
1123 continue;
1124 }
1125 input = input.field(InputValue::new(&field.name, item()));
1126 }
1127 builder = builder.register(input);
1128 }
1129 }
1130 }
1131 }
1132
1133 for function in &generated.function_fields {
1136 if function.arguments.is_empty() {
1137 continue;
1138 }
1139 let mut args = InputObject::new(format!("{}_args", function.name))
1140 .description(format!("Arguments to {}.", function.name));
1141 for (name, pg_type, _) in &function.arguments {
1142 let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1143 args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1151 }
1152 builder = builder.register(args);
1153 }
1154
1155 for (type_name, relationships) in &generated.relationship_fields {
1160 let local_type_name = generated
1161 .object_types
1162 .get(type_name)
1163 .map(|object| object.local_name.as_str())
1164 .unwrap_or(type_name);
1165 for relationship in relationships {
1166 if relationship.arguments.is_empty() {
1167 continue;
1168 }
1169 let mut args =
1170 InputObject::new(computed_args_type_name(local_type_name, &relationship.name))
1171 .description(format!(
1172 "Arguments to {}, beside the row it is asked of.",
1173 relationship.name
1174 ));
1175 for (name, pg_type, _) in &relationship.arguments {
1176 let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1177 args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1179 }
1180 builder = builder.register(args);
1181 }
1182 }
1183
1184 for object in generated.object_types.values() {
1189 let local_type_name = object.local_name.as_str();
1190 for field in &object.fields {
1191 if field.arguments.is_empty() {
1192 continue;
1193 }
1194 let mut args = InputObject::new(computed_args_type_name(local_type_name, &field.name))
1195 .description(format!(
1196 "Arguments to {}, beside the row it is asked of.",
1197 field.name
1198 ));
1199 for (name, pg_type, _) in &field.arguments {
1200 let scalar = crate::types::pg_type_to_graphql(pg_type).to_string();
1201 args = args.field(InputValue::new(name, TypeRef::named(scalar)));
1203 }
1204 builder = builder.register(args);
1205 }
1206 }
1207
1208 {
1212 use crate::input::bool_exp as be;
1213 let mut targets: BTreeMap<&str, &str> = BTreeMap::new();
1214 for relationships in generated.relationship_fields.values() {
1215 for relationship in relationships {
1216 if relationship.is_list {
1217 targets.insert(
1218 relationship.target_local_name.as_str(),
1219 relationship.target_type.as_str(),
1220 );
1221 }
1222 }
1223 }
1224 for (target, target_type) in targets {
1225 let Some(object) = generated.object_types.get(target_type) else {
1226 continue;
1227 };
1228 let booleans: Vec<&str> = object
1232 .fields
1233 .iter()
1234 .filter(|field| {
1235 object.table.get_column(&field.name).is_some()
1236 && matches!(field.graphql_type, crate::types::GraphQLType::Boolean)
1237 })
1238 .map(|field| field.name.as_str())
1239 .collect();
1240
1241 let mut over = InputObject::new(be::aggregate_bool_exp_type_name(target)).description(
1242 format!("A question about the whole set of related {} rows.", target),
1243 );
1244 for function in be::AGGREGATE_PREDICATES {
1245 if function != "count" && booleans.is_empty() {
1246 continue;
1247 }
1248 over = over.field(InputValue::new(
1249 function,
1250 TypeRef::named(be::aggregate_bool_exp_function(target, function)),
1251 ));
1252
1253 let (arguments, predicate) = match function {
1256 "count" => (
1257 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1258 target,
1259 )),
1260 be::comparison_type_name("Int"),
1261 ),
1262 _ => (
1263 TypeRef::named_nn(be::aggregate_bool_exp_columns(target, function)),
1264 be::comparison_type_name("Boolean"),
1265 ),
1266 };
1267 let mut input = InputObject::new(be::aggregate_bool_exp_function(target, function))
1268 .description(format!(
1269 "`{}` over the related {} rows, and what it has to be.",
1270 function, target
1271 ))
1272 .field(InputValue::new("arguments", arguments))
1273 .field(InputValue::new("distinct", TypeRef::named("Boolean")))
1274 .field(InputValue::new(
1275 "filter",
1276 TypeRef::named(be::bool_exp_type_name(target)),
1277 ))
1278 .field(InputValue::new("predicate", TypeRef::named_nn(predicate)));
1279 if function == "count" {
1280 input = input.description(format!(
1281 "How many related {} rows there have to be.",
1282 target
1283 ));
1284 }
1285 builder = builder.register(input);
1286
1287 if function != "count" {
1288 let mut columns = Enum::new(be::aggregate_bool_exp_columns(target, function))
1289 .description(format!("A boolean column of {}.", target));
1290 for name in &booleans {
1291 columns = columns.item(EnumItem::new(*name));
1292 }
1293 builder = builder.register(columns);
1294 }
1295 }
1296 builder = builder.register(over);
1297 }
1298 }
1299
1300 for object in generated.object_types.values() {
1304 let local_type_name = object.local_name.as_str();
1305 let updatable_columns = write_fields(object, &names, role, crate::role::Verb::Update);
1311 if object.table.unique_constraints.is_empty() {
1312 continue;
1313 }
1314
1315 let mut constraints =
1316 Enum::new(format!("{}_constraint", local_type_name)).description(format!(
1317 "A uniqueness of {} that an insert may conflict with.",
1318 local_type_name
1319 ));
1320 for (name, columns) in &object.table.unique_constraints {
1321 constraints = constraints
1322 .item(EnumItem::new(name).description(format!("unique ({})", columns.join(", "))));
1323 }
1324
1325 let mut updatable = Enum::new(format!("{}_update_column", local_type_name)).description(
1326 format!("A column of {} an upsert may write.", local_type_name),
1327 );
1328 for field in &updatable_columns {
1329 updatable = updatable.item(EnumItem::new(&field.name));
1330 }
1331 if updatable_columns.is_empty() {
1340 updatable = updatable.item(
1341 EnumItem::new(PLACEHOLDER_COLUMN)
1342 .description("No column may be written by this upsert."),
1343 );
1344 }
1345
1346 let on_conflict = InputObject::new(format!("{}_on_conflict", local_type_name))
1347 .description(format!(
1348 "What to do when an insert into {} conflicts.",
1349 local_type_name
1350 ))
1351 .field(InputValue::new(
1352 "constraint",
1353 TypeRef::named_nn(format!("{}_constraint", local_type_name)),
1354 ))
1355 .field(
1360 InputValue::new(
1361 "update_columns",
1362 TypeRef::named_nn_list_nn(format!("{}_update_column", local_type_name)),
1363 )
1364 .default_value(Value::List(Vec::new())),
1365 )
1366 .field(InputValue::new(
1367 "where",
1368 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(local_type_name)),
1369 ));
1370
1371 builder = builder
1372 .register(constraints)
1373 .register(updatable)
1374 .register(on_conflict);
1375 }
1376
1377 for (type_name, members) in &generated.enum_types {
1379 let mut generated_enum =
1380 Enum::new(type_name).description(format!("The values {} allows.", type_name));
1381 for (value, comment) in members {
1382 let item = EnumItem::new(value);
1383 generated_enum = generated_enum.item(match comment {
1384 Some(description) => item.description(description),
1385 None => item,
1386 });
1387 }
1388 builder = builder.register(generated_enum);
1389 }
1390
1391 let (order_inputs, order_enums) = crate::input::order_by::build_inputs(
1394 &generated.object_types,
1395 &generated.relationship_fields,
1396 );
1397 for input in order_inputs {
1398 builder = builder.register(input);
1399 }
1400 for enum_type in order_enums {
1401 builder = builder.register(enum_type);
1402 }
1403
1404 builder
1405 .finish()
1406 .map_err(|e| GraphQLError::SchemaError(e.to_string()))
1407}
1408
1409const PLACEHOLDER_COLUMN: &str = "_PLACEHOLDER";
1416
1417pub fn mutation_response_type_name(base_name: &str) -> String {
1419 format!("{}_mutation_response", base_name)
1420}
1421
1422fn create_mutation_response_type(base_name: &str, row_type: &str, returning: bool) -> Object {
1429 let response_name = mutation_response_type_name(base_name);
1430
1431 let response = Object::new(&response_name)
1432 .description(format!("The rows {} changed, and how many.", base_name))
1433 .field(Field::new(
1434 "affected_rows",
1435 TypeRef::named_nn(TypeRef::INT),
1436 |ctx| {
1437 FieldFuture::new(async move {
1438 if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1439 if let Some(value) = map.get(&async_graphql::Name::new("affected_rows")) {
1440 return Ok(Some(FieldValue::value(value.clone())));
1441 }
1442 }
1443 Ok(Some(FieldValue::value(Value::from(0))))
1444 })
1445 },
1446 ));
1447 if !returning {
1451 return response;
1452 }
1453 response.field(Field::new(
1454 "returning",
1455 TypeRef::named_nn_list_nn(row_type.to_string()),
1456 |ctx| {
1457 FieldFuture::new(async move {
1458 let rows = match ctx.parent_value.as_value() {
1459 Some(Value::Object(map)) => {
1460 match map.get(&async_graphql::Name::new("returning")) {
1461 Some(Value::List(items)) => items.clone(),
1462 _ => Vec::new(),
1463 }
1464 }
1465 _ => Vec::new(),
1466 };
1467 Ok(Some(FieldValue::list(
1468 rows.into_iter().map(FieldValue::value),
1469 )))
1470 })
1471 },
1472 ))
1473}
1474
1475fn create_aggregate_types(base_name: &str, object: &TableObjectType) -> Vec<Object> {
1481 use crate::schema::aggregate as agg;
1482
1483 let mut types = Vec::new();
1484
1485 let fields_type = agg::aggregate_fields_type_name(base_name);
1487 let has_rows = !object.fields.is_empty();
1491 let mut over = Object::new(agg::aggregate_type_name(base_name))
1492 .description(match has_rows {
1493 true => format!("Aggregates over {}, with the rows themselves.", base_name),
1494 false => format!("Aggregates over {}.", base_name),
1495 })
1496 .field(Field::new(
1497 "aggregate",
1498 TypeRef::named(&fields_type),
1499 |ctx| FieldFuture::new(async move { Ok(child_of(&ctx, "aggregate")) }),
1500 ));
1501 if has_rows {
1502 over = over.field(Field::new(
1503 "nodes",
1504 TypeRef::named_nn_list_nn(object.name.clone()),
1505 |ctx| {
1506 FieldFuture::new(async move {
1507 let rows = match child_value(&ctx, "nodes") {
1508 Some(Value::List(items)) => items,
1509 _ => Vec::new(),
1510 };
1511 Ok(Some(FieldValue::list(
1512 rows.into_iter().map(FieldValue::value),
1513 )))
1514 })
1515 },
1516 ));
1517 }
1518 types.push(over);
1519
1520 let mut count = Field::new("count", TypeRef::named_nn(TypeRef::INT), |ctx| {
1522 let key = ctx.ctx.field().alias().unwrap_or("count").to_string();
1527 FieldFuture::new(async move {
1528 Ok(Some(FieldValue::value(
1529 child_value(&ctx, &key).unwrap_or(Value::from(0)),
1530 )))
1531 })
1532 });
1533 if has_rows {
1539 count = count
1540 .argument(InputValue::new(
1541 "columns",
1542 TypeRef::named_nn_list(agg_select_column(base_name)),
1543 ))
1544 .argument(InputValue::new("distinct", TypeRef::named("Boolean")));
1545 }
1546 let mut aggregate_fields = Object::new(&fields_type)
1547 .description(format!("Aggregate functions over {}.", base_name))
1548 .field(count);
1549
1550 for (function, returns, columns) in agg::functions_for(object) {
1551 let function_type = agg::function_fields_type_name(base_name, function);
1552 let mut per_column = Object::new(&function_type).description(format!(
1553 "`{}` of each {} column it applies to.",
1554 function, base_name
1555 ));
1556 for column in &columns {
1557 let column_name = column.clone();
1558 let type_name = agg::field_type_for(object, column, returns);
1559 per_column =
1560 per_column.field(Field::new(column, TypeRef::named(type_name), move |ctx| {
1561 let column_name = column_name.clone();
1562 FieldFuture::new(async move {
1563 Ok(child_value(&ctx, &column_name).map(FieldValue::value))
1564 })
1565 }));
1566 }
1567 types.push(per_column);
1568
1569 let owned = function.to_string();
1570 aggregate_fields = aggregate_fields.field(Field::new(
1571 function,
1572 TypeRef::named(&function_type),
1573 move |ctx| {
1574 let owned = owned.clone();
1575 FieldFuture::new(async move { Ok(child_of(&ctx, &owned)) })
1576 },
1577 ));
1578 }
1579
1580 types.push(aggregate_fields);
1581 types
1582}
1583
1584fn agg_select_column(base_name: &str) -> String {
1586 crate::input::order_by::select_column_type_name(base_name)
1587}
1588
1589fn child_value(ctx: &ResolverContext<'_>, key: &str) -> Option<Value> {
1591 match ctx.parent_value.as_value() {
1592 Some(Value::Object(map)) => map.get(&async_graphql::Name::new(key)).cloned(),
1593 _ => None,
1594 }
1595}
1596
1597fn child_of<'a>(ctx: &ResolverContext<'_>, key: &str) -> Option<FieldValue<'a>> {
1602 match child_value(ctx, key) {
1603 None | Some(Value::Null) => None,
1604 Some(value) => Some(FieldValue::value(value)),
1605 }
1606}
1607
1608fn coded_error(code: &'static str, message: impl Into<String>) -> async_graphql::Error {
1614 let mut error = async_graphql::Error::new(message);
1615 let mut extensions = async_graphql::ErrorExtensionValues::default();
1616 extensions.set("code", code);
1617 error.extensions = Some(extensions);
1618 error
1619}
1620
1621fn validation_error(message: impl Into<String>) -> async_graphql::Error {
1623 coded_error("validation-failed", message)
1624}
1625
1626fn database_error(error: sqlx::Error) -> async_graphql::Error {
1638 let sqlx::Error::Database(db) = &error else {
1639 return async_graphql::Error::new(error.to_string());
1640 };
1641 let described = match db.code().as_deref() {
1642 Some("23505") => "Uniqueness violation. ",
1645 Some("23502") => "Not-NULL violation. ",
1646 Some("23503") => "Foreign key violation. ",
1647 Some("23514") => "Check constraint violation. ",
1648 _ => "",
1652 };
1653 let error = async_graphql::Error::new(format!("{}{}", described, db.message()));
1654 let coded = match db.code().as_deref() {
1659 Some(code) if code.starts_with("23") => Some("constraint-violation"),
1660 Some("22025") => Some("bad-request"),
1664 Some(code) if code.starts_with("22") => Some("data-exception"),
1666 _ => None,
1667 };
1668 match coded {
1669 Some(code) => at_code(error, code),
1670 None => error,
1671 }
1672}
1673
1674fn at_code(mut error: async_graphql::Error, code: &'static str) -> async_graphql::Error {
1676 let mut extensions = error.extensions.take().unwrap_or_default();
1677 extensions.set("code", code);
1678 error.extensions = Some(extensions);
1679 error
1680}
1681
1682fn at_path(mut error: async_graphql::Error, path: &str) -> async_graphql::Error {
1691 let mut extensions = error.extensions.take().unwrap_or_default();
1692 extensions.set("path", path);
1693 error.extensions = Some(extensions);
1694 error
1695}
1696
1697fn create_object_type(
1698 obj: &TableObjectType,
1699 relationships: &[RelationshipField],
1700 federation_entity: Option<&FederationEntity>,
1701) -> Object {
1702 let mut object = Object::new(&obj.name);
1703 let entity_is_shared = federation_entity.is_some_and(|entity| entity.shared);
1704 if let Some(entity) = federation_entity {
1705 object = object.key(entity.key_fields_sdl());
1706 }
1707
1708 object = match obj.description() {
1712 Some("") => object,
1715 Some(desc) => object.description(desc),
1716 None => object.description(format!(
1717 "columns and relationships of \"{}\"",
1718 obj.table_name()
1719 )),
1720 };
1721
1722 let mut taken: HashSet<String> = obj.fields.iter().map(|f| f.name.clone()).collect();
1735
1736 let mut fields: Vec<(String, Field)> = Vec::new();
1740
1741 for field in &obj.fields {
1742 let field_name = field.name.clone();
1743 let field_type = graphql_type_ref(&field.type_string());
1744 let takes_path = matches!(&field.graphql_type, crate::types::GraphQLType::Json)
1750 || matches!(
1751 &field.graphql_type,
1752 crate::types::GraphQLType::Custom(name) if name == "json"
1753 );
1754
1755 let gql_field = Field::new(&field.name, field_type, move |ctx| {
1759 let field_name = field_name.clone();
1760 FieldFuture::new(async move {
1761 if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1763 let key = async_graphql::Name::new(&field_name);
1765 let path = match takes_path {
1766 true => ctx.args.get("path").and_then(|v| v.string().ok()),
1767 false => None,
1768 };
1769 match map.get(&key) {
1770 Some(Value::Null) => return Ok(None),
1775 Some(val) => {
1776 let val = match path {
1777 Some(path) => walk_json_path(val, path)?,
1778 None => val.clone(),
1779 };
1780 return match val {
1781 Value::Null => Ok(None),
1782 val => Ok(Some(FieldValue::value(val))),
1783 };
1784 }
1785 None => {}
1786 }
1787 }
1788
1789 Ok(None)
1791 })
1792 });
1793
1794 let gql_field = match takes_path {
1795 true => gql_field.argument(
1796 InputValue::new("path", TypeRef::named("String")).description("JSON select path"),
1797 ),
1798 false => gql_field,
1799 };
1800
1801 let gql_field = match field.arguments.is_empty() {
1805 true => gql_field,
1806 false => gql_field.argument(InputValue::new(
1807 "args",
1808 TypeRef::named_nn(computed_args_type_name(&obj.local_name, &field.name)),
1809 )),
1810 };
1811
1812 let gql_field = if let Some(desc) = &field.description {
1813 gql_field.description(desc)
1814 } else {
1815 gql_field
1816 };
1817 let gql_field = if entity_is_shared && !field.is_pk {
1818 gql_field.shareable()
1819 } else {
1820 gql_field
1821 };
1822
1823 fields.push((field.name.clone(), gql_field));
1824 }
1825
1826 for rel in relationships {
1830 if !taken.insert(rel.name.clone()) {
1831 tracing::warn!(
1832 "{}: not exposing the relationship to \"{}\" as `{}` -- the table \
1833 already has a column of that name",
1834 obj.name,
1835 rel.target_type,
1836 rel.name
1837 );
1838 continue;
1839 }
1840 let field_name = rel.name.clone();
1841 let field_type = if rel.is_list {
1842 TypeRef::named_nn_list_nn(&rel.target_type)
1843 } else if always_present(rel, &obj.table) {
1844 TypeRef::named_nn(&rel.target_type)
1845 } else {
1846 TypeRef::named(&rel.target_type)
1847 };
1848
1849 let is_list = rel.is_list;
1850 let mut gql_field = Field::new(&rel.name, field_type, move |ctx| {
1851 let field_name = field_name.clone();
1852 FieldFuture::new(async move {
1853 if let Some(Value::Object(map)) = ctx.parent_value.as_value() {
1854 let key = async_graphql::Name::new(&field_name);
1855 match map.get(&key) {
1856 Some(Value::Null) if !is_list => return Ok(None),
1861 Some(val) => return Ok(Some(FieldValue::value(val.clone()))),
1862 None => {}
1863 }
1864 }
1865 Ok(None)
1866 })
1867 });
1868
1869 if !rel.arguments.is_empty() {
1880 gql_field = gql_field.argument(InputValue::new(
1881 "args",
1882 TypeRef::named_nn(computed_args_type_name(&obj.local_name, &rel.name)),
1883 ));
1884 }
1885 if rel.is_list {
1886 gql_field = gql_field
1887 .argument(InputValue::new(
1888 "distinct_on",
1889 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1890 &rel.target_local_name,
1891 )),
1892 ))
1893 .argument(InputValue::new(
1894 "where",
1895 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1896 &rel.target_local_name,
1897 )),
1898 ))
1899 .argument(InputValue::new(
1900 "order_by",
1901 TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1902 &rel.target_local_name,
1903 )),
1904 ))
1905 .argument(InputValue::new("limit", TypeRef::named("Int")))
1906 .argument(InputValue::new("offset", TypeRef::named("Int")));
1907 }
1908
1909 let gql_field = if let Some(desc) = &rel.description {
1910 gql_field.description(desc)
1911 } else {
1912 gql_field
1913 };
1914 let gql_field = if entity_is_shared {
1915 gql_field.shareable()
1916 } else {
1917 gql_field
1918 };
1919
1920 fields.push((rel.name.clone(), gql_field));
1921
1922 if rel.is_list {
1927 let aggregate_field = format!("{}_aggregate", rel.name);
1928 if taken.insert(aggregate_field.clone()) {
1929 let key = aggregate_field.clone();
1930 fields.push((
1931 aggregate_field.clone(),
1932 Field::new(
1933 &aggregate_field,
1934 TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
1935 &rel.target_local_name,
1936 )),
1937 move |ctx| {
1938 let key = key.clone();
1939 FieldFuture::new(async move {
1940 Ok(Some(FieldValue::value(
1944 child_value(&ctx, &key).unwrap_or(Value::Null),
1945 )))
1946 })
1947 },
1948 )
1949 .argument(InputValue::new(
1954 "distinct_on",
1955 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
1956 &rel.target_local_name,
1957 )),
1958 ))
1959 .argument(InputValue::new(
1960 "where",
1961 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
1962 &rel.target_local_name,
1963 )),
1964 ))
1965 .argument(InputValue::new(
1966 "order_by",
1967 TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
1968 &rel.target_local_name,
1969 )),
1970 ))
1971 .argument(InputValue::new("limit", TypeRef::named("Int")))
1972 .argument(InputValue::new("offset", TypeRef::named("Int")))
1973 .description(format!("Aggregates over {}.", rel.name)),
1974 ));
1975 if !rel.arguments.is_empty() {
1978 let last = fields.len() - 1;
1979 let (name, field) = fields.remove(last);
1980 fields.push((
1981 name,
1982 field.argument(InputValue::new(
1983 "args",
1984 TypeRef::named_nn(computed_args_type_name(&obj.local_name, &rel.name)),
1985 )),
1986 ));
1987 }
1988 if entity_is_shared {
1989 let last = fields.len() - 1;
1990 let (name, field) = fields.remove(last);
1991 fields.push((name, field.shareable()));
1992 }
1993 }
1994 }
1995 }
1996
1997 fields.sort_by(|(a, _), (b, _)| a.cmp(b));
1998 for (_, field) in fields {
1999 object = object.field(field);
2000 }
2001
2002 object
2003}
2004
2005trait RootField: Sized {
2012 fn with_argument(self, value: InputValue) -> Self;
2013}
2014
2015impl RootField for Field {
2016 fn with_argument(self, value: InputValue) -> Self {
2017 self.argument(value)
2018 }
2019}
2020
2021impl RootField for SubscriptionField {
2022 fn with_argument(self, value: InputValue) -> Self {
2023 self.argument(value)
2024 }
2025}
2026
2027fn with_row_arguments<F: RootField>(field: F, type_name: &str) -> F {
2030 with_row_arguments_named(field, type_name, true)
2031}
2032
2033fn with_row_arguments_named<F: RootField>(field: F, type_name: &str, columns: bool) -> F {
2041 let mut field = field;
2042 if columns {
2043 field = field.with_argument(InputValue::new(
2044 "distinct_on",
2045 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(type_name)),
2046 ));
2047 }
2048 field = field
2049 .with_argument(InputValue::new("limit", TypeRef::named("Int")))
2050 .with_argument(InputValue::new("offset", TypeRef::named("Int")));
2051 if columns {
2052 field = field.with_argument(InputValue::new(
2053 "order_by",
2054 TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(type_name)),
2055 ));
2056 }
2057 field.with_argument(InputValue::new(
2058 "where",
2059 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(type_name)),
2060 ))
2061}
2062
2063fn with_key_arguments<F: RootField>(
2066 field: F,
2067 schema_name: &str,
2068 table_name: &str,
2069 pk_columns: &[(String, String)],
2070 names: &crate::names::NameOverrides,
2071) -> F {
2072 let mut field = field;
2073 for (column, pg_type) in pk_columns {
2074 field = field.with_argument(InputValue::new(
2075 names
2076 .column(schema_name, table_name, column)
2077 .unwrap_or(column),
2078 TypeRef::named_nn(exposed_pk_argument_type(
2079 names,
2080 schema_name,
2081 table_name,
2082 column,
2083 pg_type,
2084 )),
2085 ));
2086 }
2087 field
2088}
2089
2090fn create_query_type(
2092 generated: &GeneratedSchema,
2093 root_name: &str,
2094 max_rows: Option<i64>,
2095 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2096 names: Arc<crate::names::NameOverrides>,
2097) -> Object {
2098 let mut roots: Vec<(String, Field)> = Vec::new();
2103
2104 for field in &generated.query_fields {
2105 let table_name = field.table_name.clone();
2106 let schema_name = field.schema_name.clone();
2107 let type_name = field.type_name.clone();
2108 let local_type_name = field.local_type_name.clone();
2109 let is_by_pk = field.is_by_pk;
2110 let pk_columns = field.pk_columns.clone();
2111 let return_type = graphql_type_ref(&field.return_type);
2112
2113 let spec_type_name = local_type_name.clone();
2114 let spec = Arc::new(QueryFieldSpec {
2115 schema_name,
2116 table_name,
2117 type_name,
2118 is_by_pk,
2119 pk_columns: pk_columns.clone(),
2120 max_rows,
2121 relationships: Arc::clone(&relationships),
2122 names: Arc::clone(&names),
2123 call: None,
2124 });
2125
2126 let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2127 let spec = Arc::clone(&spec);
2128 FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
2129 });
2130
2131 gql_field = match is_by_pk {
2133 false => with_row_arguments(gql_field, &spec_type_name),
2134 true => with_key_arguments(
2135 gql_field,
2136 &field.schema_name,
2137 &field.table_name,
2138 &pk_columns,
2139 names.as_ref(),
2140 ),
2141 };
2142
2143 if let Some(desc) = &field.description {
2144 gql_field = gql_field.description(desc);
2145 }
2146
2147 if field.rows {
2152 roots.push((field.name.clone(), gql_field));
2153 }
2154
2155 if !is_by_pk && field.aggregates {
2163 let agg_spec = Arc::new(AggregateSpec {
2164 schema_name: field.schema_name.clone(),
2165 table_name: field.table_name.clone(),
2166 type_name: field.type_name.clone(),
2167 max_rows,
2168 relationships: Arc::clone(&relationships),
2169 names: Arc::clone(&names),
2170 call: None,
2171 });
2172 let aggregate_field_name = field.aggregate_name.clone().unwrap_or_else(|| {
2173 crate::schema::aggregate::aggregate_type_name(&field.local_type_name)
2174 });
2175 let aggregate_field_name_for_sorting = aggregate_field_name.clone();
2176 let mut agg_field = Field::new(
2177 aggregate_field_name,
2178 TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2179 &field.local_type_name,
2180 )),
2181 move |ctx| {
2182 let agg_spec = Arc::clone(&agg_spec);
2183 FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
2184 },
2185 );
2186 agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2187 agg_field = match field.aggregate_description.as_deref() {
2188 Some("") => agg_field,
2189 Some(given) => agg_field.description(given),
2190 None => agg_field.description(format!(
2191 "fetch aggregated fields from the table: \"{}\"",
2192 field.table_name
2193 )),
2194 };
2195 roots.push((aggregate_field_name_for_sorting, agg_field));
2196 }
2197 }
2198
2199 roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2200 let mut query = Object::new(root_name);
2201 if roots.is_empty() {
2205 return query.field(
2206 Field::new("no_queries_available", TypeRef::named("String"), |_| {
2207 FieldFuture::new(async move { Ok(None::<Value>) })
2208 })
2209 .description("There are no queries available to the current role."),
2210 );
2211 }
2212 for (_, field) in roots {
2213 query = query.field(field);
2214 }
2215
2216 query
2217}
2218
2219type JsonbOperator = (&'static str, fn() -> TypeRef);
2221
2222const JSONB_OPERATORS: &[JsonbOperator] = &[
2229 ("_append", || TypeRef::named("jsonb")),
2230 ("_delete_at_path", || TypeRef::named_nn_list("String")),
2231 ("_delete_elem", || TypeRef::named("Int")),
2232 ("_delete_key", || TypeRef::named("String")),
2233 ("_prepend", || TypeRef::named("jsonb")),
2234];
2235
2236fn jsonb_operator_input(type_name: &str, operator: &str) -> String {
2238 format!("{}{}_input", type_name, operator)
2239}
2240
2241fn with_update_operators(
2248 field: Field,
2249 base_name: &str,
2250 has_numeric: bool,
2251 has_jsonb: bool,
2252) -> Field {
2253 let mut field = field;
2254 if has_jsonb {
2257 for (operator, _) in JSONB_OPERATORS {
2258 field = field.argument(InputValue::new(
2259 *operator,
2260 TypeRef::named(jsonb_operator_input(base_name, operator)),
2261 ));
2262 }
2263 }
2264 if has_numeric {
2265 field = field.argument(InputValue::new(
2266 "_inc",
2267 TypeRef::named(format!("{}_inc_input", base_name)),
2268 ));
2269 }
2270 field.argument(InputValue::new(
2271 "_set",
2272 TypeRef::named(format!("{}_set_input", base_name)),
2273 ))
2274}
2275
2276fn write_fields<'a>(
2286 object: &'a crate::schema::object::TableObjectType,
2287 names: &crate::names::NameOverrides,
2288 role: Option<&str>,
2289 verb: crate::role::Verb,
2290) -> Vec<&'a crate::schema::object::GraphQLField> {
2291 let allowed = role.and_then(|role| {
2292 names
2293 .permissions(&object.table.schema, &object.table.name, role)
2294 .and_then(|granted| granted.write_columns(verb))
2295 });
2296 object
2297 .writable_fields
2298 .iter()
2299 .filter(|field| {
2300 let column = table_column_for(names, &object.table, &field.name);
2301 object
2309 .table
2310 .get_column(column)
2311 .is_some_and(|c| !c.always_generated)
2312 && allowed.is_none_or(|set| set.allows(column))
2313 })
2314 .collect()
2315}
2316
2317fn update_inputs(
2319 object: &crate::schema::object::TableObjectType,
2320 names: &crate::names::NameOverrides,
2321 role: Option<&str>,
2322) -> (bool, bool) {
2323 let fields = write_fields(object, names, role, crate::role::Verb::Update);
2324 (
2325 fields
2326 .iter()
2327 .any(|f| crate::schema::aggregate::is_incrementable(&f.graphql_type)),
2328 fields
2329 .iter()
2330 .any(|f| matches!(&f.graphql_type, crate::types::GraphQLType::Json)),
2331 )
2332}
2333
2334fn create_mutation_type(
2335 generated: &GeneratedSchema,
2336 root_name: &str,
2337 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2338 type_names: Arc<HashMap<(String, String), String>>,
2339 names: Arc<crate::names::NameOverrides>,
2340 max_rows: Option<i64>,
2341 role: Option<&str>,
2342) -> Object {
2343 let mut mutation = Object::new(root_name);
2344 let mut roots: Vec<(String, Field)> = Vec::new();
2346
2347 let mut has_numeric_column: HashSet<String> = HashSet::new();
2354 let mut has_jsonb_column: HashSet<String> = HashSet::new();
2355 for object in generated.object_types.values() {
2356 let (numeric, jsonb) = update_inputs(object, &names, role);
2357 if numeric {
2358 has_numeric_column.insert(object.local_name.clone());
2359 }
2360 if jsonb {
2361 has_jsonb_column.insert(object.local_name.clone());
2362 }
2363 }
2364
2365 let has_conflict_target: HashSet<String> = generated
2366 .object_types
2367 .iter()
2368 .filter(|(_, object)| !object.table.unique_constraints.is_empty())
2369 .map(|(_, object)| object.local_name.clone())
2370 .collect();
2371
2372 for field in &generated.mutation_fields {
2373 let table_name = field.table_name.clone();
2374 let schema_name = field.schema_name.clone();
2375 let mutation_type = field.mutation_type;
2376 let pk_columns = field.pk_columns.clone();
2377 let return_type = graphql_type_ref(&field.return_type);
2378
2379 let where_type = field.local_type_name.clone();
2383
2384 let resolver_pk_columns = pk_columns.clone();
2385 let field_relationships = Arc::clone(&relationships);
2386 let field_type_names = Arc::clone(&type_names);
2387 let field_names = Arc::clone(&names);
2388 let mut gql_field = Field::new(&field.name, return_type, move |ctx| {
2389 let table_name = table_name.clone();
2390 let schema_name = schema_name.clone();
2391 let pk_columns = resolver_pk_columns.clone();
2392 let relationships = Arc::clone(&field_relationships);
2393 let type_names = Arc::clone(&field_type_names);
2394 let names = Arc::clone(&field_names);
2395 FieldFuture::new(async move {
2396 resolve_mutation(
2397 &ctx,
2398 &schema_name,
2399 &table_name,
2400 mutation_type,
2401 &pk_columns,
2402 &relationships,
2403 &type_names,
2404 &names,
2405 max_rows,
2406 )
2407 .await
2408 })
2409 });
2410
2411 match mutation_type {
2417 MutationType::Insert | MutationType::InsertOne => {
2418 let insert_input = format!("{}_insert_input", where_type);
2419 gql_field = if mutation_type == MutationType::Insert {
2420 gql_field.argument(InputValue::new(
2421 "objects",
2422 TypeRef::named_nn_list_nn(&insert_input),
2423 ))
2424 } else {
2425 gql_field.argument(InputValue::new("object", TypeRef::named_nn(&insert_input)))
2428 };
2429 if has_conflict_target.contains(&where_type) {
2430 gql_field = gql_field.argument(InputValue::new(
2431 "on_conflict",
2432 TypeRef::named(format!("{}_on_conflict", where_type)),
2433 ));
2434 }
2435 }
2436 MutationType::UpdateByPk => {
2437 gql_field = gql_field.argument(InputValue::new(
2438 "pk_columns",
2439 TypeRef::named_nn(format!("{}_pk_columns_input", where_type)),
2440 ));
2441 gql_field = with_update_operators(
2442 gql_field,
2443 &where_type,
2444 has_numeric_column.contains(&where_type),
2445 has_jsonb_column.contains(&where_type),
2446 );
2447 }
2448 MutationType::Update => {
2449 gql_field = gql_field.argument(InputValue::new(
2450 "where",
2451 TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2452 ));
2453 gql_field = with_update_operators(
2454 gql_field,
2455 &where_type,
2456 has_numeric_column.contains(&where_type),
2457 has_jsonb_column.contains(&where_type),
2458 );
2459 }
2460 MutationType::UpdateMany => {
2461 gql_field = gql_field.argument(InputValue::new(
2462 "updates",
2463 TypeRef::named_nn_list_nn(format!("{}_updates", where_type)),
2464 ));
2465 }
2466 MutationType::DeleteByPk => {
2467 for (col_name, pg_type) in &pk_columns {
2468 gql_field = gql_field.argument(InputValue::new(
2469 names
2470 .column(&field.schema_name, &field.table_name, col_name)
2471 .unwrap_or(col_name),
2472 TypeRef::named_nn(exposed_pk_argument_type(
2473 names.as_ref(),
2474 &field.schema_name,
2475 &field.table_name,
2476 col_name,
2477 pg_type,
2478 )),
2479 ));
2480 }
2481 }
2482 MutationType::Delete => {
2483 gql_field = gql_field.argument(InputValue::new(
2490 "where",
2491 TypeRef::named_nn(crate::input::bool_exp::bool_exp_type_name(&where_type)),
2492 ));
2493 }
2494 }
2495
2496 if let Some(desc) = &field.description {
2497 gql_field = gql_field.description(desc);
2498 }
2499
2500 roots.push((field.name.clone(), gql_field));
2501 }
2502
2503 roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2504 for (_, field) in roots {
2505 mutation = mutation.field(field);
2506 }
2507
2508 mutation
2509}
2510
2511fn create_subscription_type(
2528 generated: &GeneratedSchema,
2529 root_name: &str,
2530 max_rows: Option<i64>,
2531 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2532 names: Arc<crate::names::NameOverrides>,
2533 refresh: std::time::Duration,
2534) -> Subscription {
2535 let mut roots: Vec<(String, SubscriptionField)> = Vec::new();
2536
2537 for field in &generated.query_fields {
2538 let spec_type_name = field.local_type_name.clone();
2539 let pk_columns = field.pk_columns.clone();
2540 let is_by_pk = field.is_by_pk;
2541 let return_type = graphql_type_ref(&field.return_type);
2542 let watched = vec![crate::subscription::table_channel_name(
2543 &field.schema_name,
2544 &field.table_name,
2545 )];
2546
2547 let spec = Arc::new(QueryFieldSpec {
2548 schema_name: field.schema_name.clone(),
2549 table_name: field.table_name.clone(),
2550 type_name: field.type_name.clone(),
2551 is_by_pk,
2552 pk_columns: pk_columns.clone(),
2553 max_rows,
2554 relationships: Arc::clone(&relationships),
2555 names: Arc::clone(&names),
2556 call: None,
2557 });
2558
2559 let mut gql_field = SubscriptionField::new(&field.name, return_type, move |ctx| {
2560 let spec = Arc::clone(&spec);
2561 let watched = watched.clone();
2562 SubscriptionFieldFuture::new(async move {
2563 let wake = wake_on(&ctx, &watched, refresh).await;
2564 Ok(live(ctx, LiveSource::Rows(spec), wake))
2565 })
2566 });
2567
2568 gql_field = match is_by_pk {
2569 false => with_row_arguments(gql_field, &spec_type_name),
2570 true => with_key_arguments(
2571 gql_field,
2572 &field.schema_name,
2573 &field.table_name,
2574 &pk_columns,
2575 names.as_ref(),
2576 ),
2577 };
2578 if let Some(desc) = &field.description {
2579 gql_field = gql_field.description(desc);
2580 }
2581 if field.rows {
2583 roots.push((field.name.clone(), gql_field));
2584 }
2585
2586 if is_by_pk || !field.aggregates {
2589 continue;
2590 }
2591
2592 let agg_spec = Arc::new(AggregateSpec {
2594 schema_name: field.schema_name.clone(),
2595 table_name: field.table_name.clone(),
2596 type_name: field.type_name.clone(),
2597 max_rows,
2598 relationships: Arc::clone(&relationships),
2599 names: Arc::clone(&names),
2600 call: None,
2601 });
2602 let agg_watched = vec![crate::subscription::table_channel_name(
2603 &field.schema_name,
2604 &field.table_name,
2605 )];
2606 let agg_name = field.aggregate_name.clone().unwrap_or_else(|| {
2607 crate::schema::aggregate::aggregate_type_name(&field.local_type_name)
2608 });
2609 let mut agg_field = SubscriptionField::new(
2610 agg_name.clone(),
2611 TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
2612 &field.local_type_name,
2613 )),
2614 move |ctx| {
2615 let agg_spec = Arc::clone(&agg_spec);
2616 let watched = agg_watched.clone();
2617 SubscriptionFieldFuture::new(async move {
2618 let wake = wake_on(&ctx, &watched, refresh).await;
2619 Ok(live(ctx, LiveSource::Aggregate(agg_spec), wake))
2620 })
2621 },
2622 );
2623 agg_field = with_row_arguments_named(agg_field, &spec_type_name, field.rows);
2624 agg_field = match field.aggregate_description.as_deref() {
2625 Some("") => agg_field,
2626 Some(given) => agg_field.description(given),
2627 None => agg_field.description(format!(
2628 "fetch aggregated fields from the table: \"{}\"",
2629 field.table_name
2630 )),
2631 };
2632 roots.push((agg_name, agg_field));
2633 }
2634
2635 roots.sort_by(|(a, _), (b, _)| a.cmp(b));
2636 let mut subscription = Subscription::new(root_name);
2637 if roots.is_empty() {
2640 return subscription.field(SubscriptionField::new(
2641 "no_subscriptions_available",
2642 TypeRef::named("String"),
2643 |_| {
2644 SubscriptionFieldFuture::new(async move {
2645 Ok(futures::stream::empty::<
2646 Result<FieldValue, async_graphql::Error>,
2647 >())
2648 })
2649 },
2650 ));
2651 }
2652 for (_, field) in roots {
2653 subscription = subscription.field(field);
2654 }
2655 subscription
2656}
2657
2658async fn wake_on(
2664 ctx: &ResolverContext<'_>,
2665 channels: &[String],
2666 refresh: std::time::Duration,
2667) -> futures::stream::BoxStream<'static, ()> {
2668 use futures::StreamExt;
2669
2670 let mut streams: Vec<futures::stream::BoxStream<'static, ()>> =
2671 Vec::with_capacity(channels.len() + 1);
2672 streams.push(
2675 tokio_stream::wrappers::IntervalStream::new({
2676 let mut interval = tokio::time::interval(refresh);
2677 interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
2678 interval
2679 })
2680 .map(|_| ())
2681 .boxed(),
2682 );
2683
2684 if let Ok(broker_arc) = ctx.data::<Arc<RwLock<Option<NotifyBroker>>>>() {
2685 let guard = broker_arc.read().await;
2686 if let Some(broker) = guard.as_ref() {
2687 for channel in channels {
2688 streams.push(
2692 broker
2693 .subscribe_or_create(channel)
2694 .await
2695 .map(|_| ())
2696 .boxed(),
2697 );
2698 }
2699 }
2700 }
2701
2702 futures::stream::select_all(streams).boxed()
2703}
2704
2705enum LiveSource {
2711 Rows(Arc<QueryFieldSpec>),
2712 Aggregate(Arc<AggregateSpec>),
2713}
2714
2715async fn read_live<'a>(
2717 ctx: &ResolverContext<'a>,
2718 source: &LiveSource,
2719) -> Result<(serde_json::Value, Option<FieldValue<'a>>), async_graphql::Error> {
2720 match source {
2721 LiveSource::Rows(spec) => {
2722 let rows = query_rows(ctx, spec).await?;
2723 Ok((
2724 serde_json::Value::Array(rows.clone()),
2725 rows_as_field_value(rows, spec.is_by_pk),
2726 ))
2727 }
2728 LiveSource::Aggregate(spec) => {
2729 let value = aggregate_value(ctx, spec).await?;
2730 let seen = serde_json::to_value(&value).unwrap_or_default();
2731 Ok((seen, Some(FieldValue::value(value))))
2732 }
2733 }
2734}
2735
2736fn live<'a>(
2744 ctx: ResolverContext<'a>,
2745 source: LiveSource,
2746 wake: futures::stream::BoxStream<'static, ()>,
2747) -> impl futures::Stream<Item = Result<FieldValue<'a>, async_graphql::Error>> + Send + 'a {
2748 use futures::StreamExt;
2749
2750 struct Live<'a> {
2751 ctx: ResolverContext<'a>,
2752 source: LiveSource,
2753 wake: futures::stream::BoxStream<'static, ()>,
2754 sent: Option<serde_json::Value>,
2755 started: bool,
2756 failed: bool,
2758 _active: crate::subscription::ActiveSubscription,
2760 }
2761
2762 futures::stream::unfold(
2763 Live {
2764 ctx,
2765 source,
2766 wake,
2767 sent: None,
2768 started: false,
2769 failed: false,
2770 _active: crate::subscription::ActiveSubscription::open(),
2771 },
2772 |mut live: Live<'a>| async move {
2773 if live.failed {
2774 return None;
2775 }
2776 loop {
2777 if live.started {
2778 live.wake.next().await?;
2779 } else {
2780 live.started = true;
2781 }
2782 let read = read_live(&live.ctx, &live.source).await;
2783 match read {
2784 Err(e) => {
2792 live.failed = true;
2793 return Some((Err(e), live));
2794 }
2795 Ok((seen, value)) => {
2796 if live.sent.as_ref() == Some(&seen) {
2797 continue;
2798 }
2799 live.sent = Some(seen);
2800 let value = value.unwrap_or_else(|| FieldValue::value(Value::Null));
2801 return Some((Ok(value), live));
2802 }
2803 }
2804 }
2805 },
2806 )
2807}
2808
2809struct QueryFieldSpec {
2811 schema_name: String,
2812 table_name: String,
2813 type_name: String,
2814 is_by_pk: bool,
2815 pk_columns: Vec<(String, String)>,
2816 max_rows: Option<i64>,
2817 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2818 names: Arc<crate::names::NameOverrides>,
2819 call: Option<Arc<FunctionCall>>,
2822}
2823
2824#[derive(Clone)]
2826struct EntityLookup {
2827 schema_name: String,
2828 table_name: String,
2829 type_name: String,
2830 key_columns: Vec<EntityKeyColumn>,
2831 row_column_types: HashMap<String, String>,
2832}
2833
2834#[derive(Clone)]
2835struct EntityKeyColumn {
2836 field_name: String,
2837 column_name: String,
2838 pg_type: String,
2839 explicit_id: bool,
2840}
2841
2842struct EntityResolverState {
2844 entities: HashMap<String, EntityLookup>,
2845 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2846 names: Arc<crate::names::NameOverrides>,
2847 max_rows: Option<i64>,
2848}
2849
2850impl EntityResolverState {
2851 fn new(
2852 generated: &GeneratedSchema,
2853 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2854 names: Arc<crate::names::NameOverrides>,
2855 max_rows: Option<i64>,
2856 ) -> Self {
2857 let mut entities = HashMap::new();
2858 for (type_name, entity) in &generated.federation_entities {
2859 let Some(object) = generated.object_types.get(type_name) else {
2860 continue;
2861 };
2862 let mut key_columns = Vec::with_capacity(entity.key_fields.len());
2863 for field_name in &entity.key_fields {
2864 let column_name = names
2865 .column_source(&object.table.schema, &object.table.name, field_name)
2866 .unwrap_or(field_name);
2867 let Some(column) = object.table.get_column(column_name) else {
2868 continue;
2869 };
2870 key_columns.push(EntityKeyColumn {
2871 field_name: field_name.clone(),
2872 column_name: column.name.clone(),
2873 pg_type: column.nominal_type.clone(),
2874 explicit_id: matches!(
2875 names.column_type(&object.table.schema, &object.table.name, &column.name),
2876 Some(crate::types::GraphQLType::Id)
2877 ),
2878 });
2879 }
2880 if key_columns.len() != entity.key_fields.len() {
2881 continue;
2882 }
2883 entities.insert(
2884 type_name.clone(),
2885 EntityLookup {
2886 schema_name: object.table.schema.clone(),
2887 table_name: object.table.name.clone(),
2888 type_name: type_name.clone(),
2889 key_columns,
2890 row_column_types: exposed_column_types(&object.table, names.as_ref()),
2891 },
2892 );
2893 }
2894 Self {
2895 entities,
2896 relationships,
2897 names,
2898 max_rows,
2899 }
2900 }
2901}
2902
2903struct EntityRepresentation {
2904 index: usize,
2905 type_name: String,
2906 values: serde_json::Map<String, serde_json::Value>,
2907}
2908
2909fn entity_field_applicability(
2916 selection_set: &SelectionSet,
2917 fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2918 type_name: &str,
2919) -> Vec<bool> {
2920 fn condition_matches(condition: Option<&Positioned<TypeCondition>>, type_name: &str) -> bool {
2921 condition.is_none_or(|condition| {
2922 let condition = condition.node.on.node.as_str();
2923 condition == type_name || condition == "_Entity"
2924 })
2925 }
2926
2927 fn collect(
2928 selection_set: &SelectionSet,
2929 fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2930 type_name: &str,
2931 parent_matches: bool,
2932 applicability: &mut Vec<bool>,
2933 ) {
2934 for selection in &selection_set.items {
2935 match &selection.node {
2936 Selection::Field(_) => applicability.push(parent_matches),
2937 Selection::FragmentSpread(spread) => {
2938 let Some(fragment) = fragments.get(&spread.node.fragment_name.node) else {
2939 continue;
2940 };
2941 collect(
2942 &fragment.node.selection_set.node,
2943 fragments,
2944 type_name,
2945 parent_matches
2946 && condition_matches(Some(&fragment.node.type_condition), type_name),
2947 applicability,
2948 );
2949 }
2950 Selection::InlineFragment(fragment) => collect(
2951 &fragment.node.selection_set.node,
2952 fragments,
2953 type_name,
2954 parent_matches
2955 && condition_matches(fragment.node.type_condition.as_ref(), type_name),
2956 applicability,
2957 ),
2958 }
2959 }
2960 }
2961
2962 let mut applicability = Vec::new();
2963 collect(
2964 selection_set,
2965 fragments,
2966 type_name,
2967 true,
2968 &mut applicability,
2969 );
2970 applicability
2971}
2972
2973fn entity_selection_fields<'ctx, 'a>(
2974 ctx: &'ctx ResolverContext<'a>,
2975 type_name: &str,
2976) -> Vec<SelectionField<'ctx>> {
2977 let applicability = entity_field_applicability(
2978 &ctx.ctx.item.node.selection_set.node,
2979 &ctx.ctx.query_env.fragments,
2980 type_name,
2981 );
2982 let fields: Vec<_> = ctx.field().selection_set().collect();
2983 debug_assert_eq!(fields.len(), applicability.len());
2984 fields
2985 .into_iter()
2986 .zip(applicability)
2987 .filter_map(|(field, applies)| applies.then_some(field))
2988 .collect()
2989}
2990
2991async fn resolve_entities<'a>(
2992 ctx: &ResolverContext<'a>,
2993 state: &EntityResolverState,
2994) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
2995 let representations = ctx.args.try_get("representations")?.list()?;
2996 let mut grouped: HashMap<String, Vec<EntityRepresentation>> = HashMap::new();
2997 let mut count = 0;
2998 for (index, representation) in representations.iter().enumerate() {
2999 count += 1;
3000 let value = accessor_to_json(&representation);
3001 let serde_json::Value::Object(values) = value else {
3002 return Err(async_graphql::Error::new(
3003 "entity representation must be an object",
3004 ));
3005 };
3006 let type_name = values
3007 .get("__typename")
3008 .and_then(serde_json::Value::as_str)
3009 .ok_or_else(|| {
3010 async_graphql::Error::new("entity representation is missing __typename")
3011 })?
3012 .to_string();
3013 grouped
3014 .entry(type_name.clone())
3015 .or_default()
3016 .push(EntityRepresentation {
3017 index,
3018 type_name,
3019 values,
3020 });
3021 }
3022
3023 let mut resolved: Vec<Option<FieldValue<'a>>> =
3024 std::iter::repeat_with(|| None).take(count).collect();
3025 for (type_name, representations) in grouped {
3026 let lookup = state.entities.get(&type_name).ok_or_else(|| {
3027 async_graphql::Error::new(format!("unknown federation entity \"{}\"", type_name))
3028 })?;
3029 let rows = resolve_entity_group(ctx, lookup, &representations, state).await?;
3030 for (ordinality, representation) in representations.into_iter().enumerate() {
3031 resolved[representation.index] = Some(match &rows[ordinality] {
3032 Some(row) => FieldValue::value(json_to_value(row.clone()))
3033 .with_type(representation.type_name),
3034 None => FieldValue::NULL,
3035 });
3036 }
3037 }
3038
3039 Ok(Some(FieldValue::list(resolved.into_iter().map(|value| {
3040 value.expect("every representation was resolved or errored")
3041 }))))
3042}
3043
3044async fn resolve_entity_group(
3045 ctx: &ResolverContext<'_>,
3046 lookup: &EntityLookup,
3047 representations: &[EntityRepresentation],
3048 state: &EntityResolverState,
3049) -> Result<Vec<Option<serde_json::Value>>, async_graphql::Error> {
3050 if representations.is_empty() {
3051 return Ok(Vec::new());
3052 }
3053
3054 let pool = ctx.data::<PgPool>()?;
3055 let gql_ctx = ctx.data::<GraphQLContext>()?;
3056 let mut bound_values = Vec::new();
3057 let mut value_rows = Vec::with_capacity(representations.len());
3058 for (ordinality, representation) in representations.iter().enumerate() {
3059 let normalized_values = normalized_entity_key_values(
3060 &representation.values,
3061 &lookup.key_columns,
3062 &lookup.type_name,
3063 )?;
3064 let mut values = Vec::with_capacity(lookup.key_columns.len() + 1);
3065 for (key_column, value) in lookup.key_columns.iter().zip(normalized_values) {
3066 values.push(format!(
3067 "${}::{}",
3068 bound_values.len() + 1,
3069 key_column.pg_type
3070 ));
3071 bound_values.push(value);
3072 }
3073 values.push(ordinality.to_string());
3074 value_rows.push(format!("({})", values.join(", ")));
3075 }
3076
3077 let representation_alias = "pgrst_entity_representation";
3078 let ordinality_column = "pgrst_entity_ordinality";
3079 let key_aliases: Vec<String> = (0..lookup.key_columns.len())
3080 .map(|index| format!("pgrst_entity_key_{index}"))
3081 .collect();
3082 let mut representation_columns: Vec<String> = key_aliases
3083 .iter()
3084 .map(|column| postrust_sql::escape_ident(column))
3085 .collect();
3086 representation_columns.push(postrust_sql::escape_ident(ordinality_column));
3087 let representations_sql = format!(
3088 "(VALUES {}) AS {} ({})",
3089 value_rows.join(", "),
3090 postrust_sql::escape_ident(representation_alias),
3091 representation_columns.join(", ")
3092 );
3093 let key_predicate = lookup
3094 .key_columns
3095 .iter()
3096 .zip(&key_aliases)
3097 .map(|(key_column, key_alias)| {
3098 format!(
3099 "{}.{} = {}.{}",
3100 postrust_sql::escape_ident(READ_ROW),
3101 postrust_sql::escape_ident(&key_column.column_name),
3102 postrust_sql::escape_ident(representation_alias),
3103 postrust_sql::escape_ident(key_alias)
3104 )
3105 })
3106 .collect::<Vec<_>>()
3107 .join(" AND ");
3108 let mut where_sql = format!(" WHERE ({key_predicate})");
3109
3110 let permission = permission_predicate(
3111 &gql_ctx.caller(),
3112 state.names.as_ref(),
3113 &lookup.schema_name,
3114 &lookup.table_name,
3115 )?;
3116 if let Some(predicate) = permission {
3117 let guard = gql_ctx
3118 .schema_cache
3119 .get()
3120 .await
3121 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3122 let cache = guard
3123 .as_ref()
3124 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3125 let scope = WhereScope::table(
3126 &lookup.schema_name,
3127 &lookup.table_name,
3128 &lookup.type_name,
3129 state.names.as_ref(),
3130 )
3131 .under_alias(READ_ROW)
3132 .with_resolution(cache, state.relationships.as_ref())
3133 .for_caller(gql_ctx.caller());
3134 let (filter_sql, filter_values) =
3135 build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3136 if !filter_sql.is_empty() {
3137 where_sql = format!(
3138 "{} AND ({})",
3139 where_sql,
3140 filter_sql.trim_start_matches("WHERE ")
3141 );
3142 bound_values.extend(filter_values);
3143 }
3144 }
3145
3146 let source = format!(
3147 "{}.{} AS {}",
3148 postrust_sql::escape_ident(&lookup.schema_name),
3149 postrust_sql::escape_ident(&lookup.table_name),
3150 postrust_sql::escape_ident(READ_ROW)
3151 );
3152 let source_rows = format!(
3153 "SELECT {}.* FROM {}{}",
3154 postrust_sql::escape_ident(READ_ROW),
3155 source,
3156 where_sql
3157 );
3158 let mut projection = {
3159 let guard = gql_ctx
3160 .schema_cache
3161 .get()
3162 .await
3163 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3164 guard
3165 .as_ref()
3166 .and_then(|cache| {
3167 cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3168 &lookup.schema_name,
3169 &lookup.table_name,
3170 ))
3171 })
3172 .and_then(|table| rename_projection(table, "src", state.names.as_ref()))
3173 .unwrap_or_else(|| "src.*".to_string())
3174 };
3175 let projected_rows = {
3176 let guard = gql_ctx
3177 .schema_cache
3178 .get()
3179 .await
3180 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3181 let cache = guard
3182 .as_ref()
3183 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3184 let table = cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3185 &lookup.schema_name,
3186 &lookup.table_name,
3187 ));
3188 let selected_fields = entity_selection_fields(ctx, &lookup.type_name);
3189 let mut param_idx = bound_values.len() + 1;
3190 let computed = match table {
3191 Some(table) => computed_projections(
3192 table,
3193 selected_fields.iter().copied(),
3194 "src",
3195 state.names.as_ref(),
3196 cache,
3197 &mut param_idx,
3198 &mut bound_values,
3199 )?,
3200 None => Vec::new(),
3201 };
3202 let embeds = build_embed_expressions(
3203 &gql_ctx.caller(),
3204 cache,
3205 state.relationships.as_ref(),
3206 &lookup.type_name,
3207 "src",
3208 selected_fields,
3209 state.max_rows,
3210 &mut 0,
3211 &mut param_idx,
3212 &mut bound_values,
3213 state.names.as_ref(),
3214 )?;
3215 for expression in &computed {
3216 projection.push_str(", ");
3217 projection.push_str(expression);
3218 }
3219 for (field_name, expression) in &embeds {
3220 projection.push_str(", ");
3221 projection.push_str(expression);
3222 projection.push_str(" AS ");
3223 projection.push_str(&postrust_sql::escape_ident(field_name));
3224 }
3225 format!("SELECT {} FROM ({}) AS src", projection, source_rows)
3226 };
3227 let sql = format!(
3232 "SELECT json_build_array({}.{}, {}) FROM {} CROSS JOIN LATERAL ({}) AS t",
3233 postrust_sql::escape_ident(representation_alias),
3234 postrust_sql::escape_ident(ordinality_column),
3235 row_json("t", &lookup.row_column_types),
3236 representations_sql,
3237 projected_rows,
3238 );
3239 let mut tx = begin_with_session(pool, gql_ctx).await?;
3240 let rows = execute_query_on(&mut tx, &sql, &bound_values).await?;
3241 tx.commit().await?;
3242
3243 let mut by_ordinal = std::iter::repeat_with(|| None)
3244 .take(representations.len())
3245 .collect::<Vec<_>>();
3246 for result in rows {
3247 let serde_json::Value::Array(mut pair) = result else {
3248 return Err(async_graphql::Error::new(
3249 "entity query returned an invalid result",
3250 ));
3251 };
3252 if pair.len() != 2 {
3253 return Err(async_graphql::Error::new(
3254 "entity query returned an invalid result",
3255 ));
3256 }
3257 let row = pair.pop().expect("the result pair has two values");
3258 let ordinality = pair
3259 .pop()
3260 .and_then(|value| value.as_u64())
3261 .and_then(|value| usize::try_from(value).ok())
3262 .filter(|value| *value < by_ordinal.len())
3263 .ok_or_else(|| async_graphql::Error::new("entity query returned an invalid ordinal"))?;
3264 by_ordinal[ordinality] = Some(row);
3265 }
3266 Ok(by_ordinal)
3267}
3268
3269fn normalized_entity_key_values(
3273 values: &serde_json::Map<String, serde_json::Value>,
3274 key_columns: &[EntityKeyColumn],
3275 type_name: &str,
3276) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
3277 let mut key = Vec::with_capacity(key_columns.len());
3278 for key_column in key_columns {
3279 let value = values.get(&key_column.field_name).ok_or_else(|| {
3280 async_graphql::Error::new(format!(
3281 "entity representation for \"{}\" is missing key field \"{}\"",
3282 type_name, key_column.field_name
3283 ))
3284 })?;
3285 key.push(normalize_entity_key_value(type_name, key_column, value)?);
3286 }
3287 Ok(key)
3288}
3289
3290fn normalize_entity_key_value(
3291 type_name: &str,
3292 key_column: &EntityKeyColumn,
3293 value: &serde_json::Value,
3294) -> Result<serde_json::Value, async_graphql::Error> {
3295 let invalid = |expected: &str| {
3296 async_graphql::Error::new(format!(
3297 "entity representation for \"{}\" has invalid key field \"{}\": expected {}, found {}",
3298 type_name,
3299 key_column.field_name,
3300 expected,
3301 json_value_kind(value)
3302 ))
3303 };
3304
3305 if key_column.explicit_id {
3306 return normalize_graphql_id(value, &key_column.pg_type).ok_or_else(|| {
3307 invalid(&format!(
3308 "an ID compatible with PostgreSQL type \"{}\"",
3309 key_column.pg_type
3310 ))
3311 });
3312 }
3313
3314 let pg_type = key_column.pg_type.trim().to_ascii_lowercase();
3315 let normalized = if matches!(pg_type.as_str(), "int8" | "bigint") {
3316 match value {
3317 serde_json::Value::Number(number) => number.as_i64(),
3318 serde_json::Value::String(text) => text.parse::<i64>().ok(),
3319 _ => None,
3320 }
3321 .map(serde_json::Value::from)
3322 } else if integer_bounds(&pg_type).is_some() {
3323 value.as_i64().and_then(|integer| {
3324 let (min, max) = integer_bounds(&pg_type)?;
3325 (min..=max)
3326 .contains(&integer)
3327 .then(|| serde_json::Value::from(integer))
3328 })
3329 } else if matches!(
3330 pg_type.as_str(),
3331 "float4" | "float8" | "real" | "double precision"
3332 ) {
3333 value
3334 .as_f64()
3335 .and_then(serde_json::Number::from_f64)
3336 .map(serde_json::Value::Number)
3337 } else if matches!(pg_type.as_str(), "numeric" | "decimal") {
3338 match value {
3339 serde_json::Value::Number(number) => {
3340 Some(serde_json::Value::String(number.to_string()))
3341 }
3342 serde_json::Value::String(text) if text.parse::<sqlx::types::BigDecimal>().is_ok() => {
3343 Some(serde_json::Value::String(text.clone()))
3344 }
3345 _ => None,
3346 }
3347 } else if matches!(pg_type.as_str(), "bool" | "boolean") {
3348 value.as_bool().map(serde_json::Value::Bool)
3349 } else if matches!(pg_type.as_str(), "json" | "jsonb") {
3350 Some(value.clone())
3351 } else if is_array_type(&pg_type) {
3352 value.is_array().then(|| value.clone())
3353 } else {
3354 value
3355 .as_str()
3356 .map(|text| serde_json::Value::String(text.to_string()))
3357 };
3358
3359 normalized.ok_or_else(|| {
3360 invalid(&format!(
3361 "a value compatible with PostgreSQL type \"{}\"",
3362 key_column.pg_type
3363 ))
3364 })
3365}
3366
3367fn normalize_graphql_id(value: &serde_json::Value, pg_type: &str) -> Option<serde_json::Value> {
3368 if integer_bounds(pg_type).is_some() {
3369 let integer = match value {
3370 serde_json::Value::Number(number) => number.as_i64(),
3371 serde_json::Value::String(text) => text.parse::<i64>().ok(),
3372 _ => None,
3373 }?;
3374 let (min, max) = integer_bounds(pg_type)?;
3375 return (min..=max)
3376 .contains(&integer)
3377 .then(|| serde_json::Value::from(integer));
3378 }
3379 if pg_type.eq_ignore_ascii_case("uuid") {
3380 return normalize_uuid(value).map(serde_json::Value::String);
3381 }
3382 if is_text_type(pg_type) {
3383 return match value {
3384 serde_json::Value::String(s) => Some(serde_json::Value::String(s.clone())),
3385 serde_json::Value::Number(n) if n.is_i64() || n.is_u64() => {
3386 Some(serde_json::Value::String(n.to_string()))
3387 }
3388 _ => None,
3389 };
3390 }
3391 None
3392}
3393
3394fn integer_bounds(pg_type: &str) -> Option<(i64, i64)> {
3395 match pg_type.trim().to_ascii_lowercase().as_str() {
3396 "int2" | "smallint" => Some((i16::MIN.into(), i16::MAX.into())),
3397 "int4" | "int" | "integer" => Some((i32::MIN.into(), i32::MAX.into())),
3398 "int8" | "bigint" => Some((i64::MIN, i64::MAX)),
3399 _ => None,
3400 }
3401}
3402
3403fn is_text_type(pg_type: &str) -> bool {
3404 matches!(
3405 pg_type.trim().to_ascii_lowercase().as_str(),
3406 "text" | "varchar" | "character varying" | "char" | "character" | "bpchar" | "citext"
3407 )
3408}
3409
3410fn normalize_uuid(value: &serde_json::Value) -> Option<String> {
3411 sqlx::types::Uuid::parse_str(value.as_str()?)
3412 .ok()
3413 .map(|uuid| uuid.to_string())
3414}
3415
3416fn json_value_kind(value: &serde_json::Value) -> &'static str {
3417 match value {
3418 serde_json::Value::Null => "null",
3419 serde_json::Value::Bool(_) => "a boolean",
3420 serde_json::Value::Number(_) => "a number",
3421 serde_json::Value::String(_) => "a string",
3422 serde_json::Value::Array(_) => "an array",
3423 serde_json::Value::Object(_) => "an object",
3424 }
3425}
3426
3427fn add_function_fields(
3434 mut root: Object,
3435 generated: &GeneratedSchema,
3436 volatile: bool,
3437 max_rows: Option<i64>,
3438 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3439 names: Arc<crate::names::NameOverrides>,
3440) -> Object {
3441 let taken: HashSet<&str> = if volatile {
3446 generated
3447 .mutation_fields
3448 .iter()
3449 .map(|f| f.name.as_str())
3450 .collect()
3451 } else {
3452 generated
3453 .query_fields
3454 .iter()
3455 .map(|f| f.name.as_str())
3456 .collect()
3457 };
3458
3459 for function in &generated.function_fields {
3460 if function.volatile != volatile {
3461 continue;
3462 }
3463 if taken.contains(function.name.as_str()) {
3464 tracing::warn!(
3465 "not exposing {}.{}({}) -- a table already has a root field \
3466 called {}",
3467 function.schema_name,
3468 function.function_name,
3469 function.argument_signature(),
3470 function.name,
3471 );
3472 continue;
3473 }
3474 let spec = Arc::new(QueryFieldSpec {
3475 schema_name: function.returns_table.0.clone(),
3476 table_name: function.returns_table.1.clone(),
3477 type_name: function.returns.clone(),
3478 is_by_pk: false,
3479 pk_columns: Vec::new(),
3480 max_rows,
3481 relationships: Arc::clone(&relationships),
3482 names: Arc::clone(&names),
3483 call: Some(Arc::new(FunctionCall {
3484 schema: function.schema_name.clone(),
3485 name: function.function_name.clone(),
3486 arguments: function.arguments.clone(),
3487 session_argument: function.session_argument.clone(),
3488 })),
3489 });
3490
3491 let mut field = Field::new(
3492 &function.name,
3493 TypeRef::named_nn_list_nn(&function.returns),
3494 move |ctx| {
3495 let spec = Arc::clone(&spec);
3496 FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
3497 },
3498 );
3499 if !function.arguments.is_empty() {
3500 field = field.argument(InputValue::new(
3501 "args",
3502 TypeRef::named_nn(format!("{}_args", function.name)),
3503 ));
3504 }
3505 field = field
3506 .argument(InputValue::new(
3507 "where",
3508 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
3509 &function.local_returns,
3510 )),
3511 ))
3512 .argument(InputValue::new(
3513 "order_by",
3514 TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
3515 &function.local_returns,
3516 )),
3517 ))
3518 .argument(InputValue::new(
3519 "distinct_on",
3520 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
3521 &function.local_returns,
3522 )),
3523 ))
3524 .argument(InputValue::new("limit", TypeRef::named("Int")))
3525 .argument(InputValue::new("offset", TypeRef::named("Int")));
3526 if let Some(description) = &function.description {
3527 field = field.description(description);
3528 }
3529 root = root.field(field);
3530
3531 let agg_spec = Arc::new(AggregateSpec {
3536 schema_name: function.returns_table.0.clone(),
3537 table_name: function.returns_table.1.clone(),
3538 type_name: function.returns.clone(),
3539 max_rows,
3540 relationships: Arc::clone(&relationships),
3541 names: Arc::clone(&names),
3542 call: Some(Arc::new(FunctionCall {
3543 schema: function.schema_name.clone(),
3544 name: function.function_name.clone(),
3545 arguments: function.arguments.clone(),
3546 session_argument: function.session_argument.clone(),
3547 })),
3548 });
3549 let mut aggregate = Field::new(
3550 format!("{}_aggregate", function.name),
3551 TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
3552 &function.local_returns,
3553 )),
3554 move |ctx| {
3555 let agg_spec = Arc::clone(&agg_spec);
3556 FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
3557 },
3558 );
3559 if !function.arguments.is_empty() {
3560 aggregate = aggregate.argument(InputValue::new(
3561 "args",
3562 TypeRef::named_nn(format!("{}_args", function.name)),
3563 ));
3564 }
3565 aggregate = with_row_arguments(aggregate, &function.local_returns).description(format!(
3566 "fetch aggregated fields from the table: \"{}\"",
3567 function.returns_table.1
3568 ));
3569 root = root.field(aggregate);
3570 }
3571 root
3572}
3573
3574fn function_source(
3582 ctx: &ResolverContext<'_>,
3583 call: &FunctionCall,
3584 alias: &str,
3585 bound_values: &mut Vec<serde_json::Value>,
3586) -> Result<String, async_graphql::Error> {
3587 let given = ctx
3588 .args
3589 .try_get("args")
3590 .ok()
3591 .map(|v| accessor_to_json(&v))
3592 .unwrap_or_else(|| serde_json::json!({}));
3593 let mut passed = Vec::new();
3594 for (name, _, required) in &call.arguments {
3595 match given.get(name) {
3596 None if *required => {
3597 return Err(async_graphql::Error::new(format!(
3598 "{} needs the argument \"{}\"",
3599 call.name, name
3600 )))
3601 }
3602 None => continue,
3605 Some(value) => {
3606 let (_, pg_type, _) = call
3613 .arguments
3614 .iter()
3615 .find(|(argument, _, _)| argument == name)
3616 .expect("iterating the arguments");
3617 passed.push(format!(
3618 "{} => ${}::{}",
3619 postrust_sql::escape_ident(name),
3620 bound_values.len() + 1,
3621 pg_type
3622 ));
3623 bound_values.push(value.clone());
3624 }
3625 }
3626 }
3627 if let Some(session) = &call.session_argument {
3631 passed.push(format!(
3632 "{} => {}",
3633 postrust_sql::escape_ident(session),
3634 SESSION_ARGUMENT
3635 ));
3636 }
3637 Ok(format!(
3638 "{}.{}({}) AS {}",
3639 postrust_sql::escape_ident(&call.schema),
3640 postrust_sql::escape_ident(&call.name),
3641 passed.join(", "),
3642 postrust_sql::escape_ident(alias)
3643 ))
3644}
3645
3646struct FunctionCall {
3648 schema: String,
3649 name: String,
3650 arguments: Vec<(String, String, bool)>,
3651 session_argument: Option<String>,
3654}
3655
3656struct AggregateSpec {
3658 schema_name: String,
3659 table_name: String,
3660 type_name: String,
3661 max_rows: Option<i64>,
3662 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3663 names: Arc<crate::names::NameOverrides>,
3664 call: Option<Arc<FunctionCall>>,
3668}
3669
3670async fn resolve_aggregate<'a>(
3677 ctx: &ResolverContext<'a>,
3678 spec: &AggregateSpec,
3679) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
3680 Ok(Some(FieldValue::value(aggregate_value(ctx, spec).await?)))
3681}
3682
3683async fn aggregate_value(
3688 ctx: &ResolverContext<'_>,
3689 spec: &AggregateSpec,
3690) -> Result<Value, async_graphql::Error> {
3691 use crate::schema::aggregate as agg;
3692
3693 let pool = ctx.data::<PgPool>()?;
3694 let gql_ctx = ctx.data::<GraphQLContext>()?;
3695
3696 let mut bound_values: Vec<serde_json::Value> = Vec::new();
3697 let source = match &spec.call {
3701 None => format!(
3702 "{}.{}",
3703 postrust_sql::escape_ident(&spec.schema_name),
3704 postrust_sql::escape_ident(&spec.table_name)
3705 ),
3706 Some(call) => function_source(ctx, call, &spec.table_name, &mut bound_values)?,
3707 };
3708
3709 let mut where_sql = String::new();
3710 {
3711 let requested = ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v));
3715 let permission = permission_predicate(
3716 &gql_ctx.caller(),
3717 spec.names.as_ref(),
3718 &spec.schema_name,
3719 &spec.table_name,
3720 )?;
3721 if let Some(predicate) = and_predicates(requested, permission) {
3722 let guard = gql_ctx
3723 .schema_cache
3724 .get()
3725 .await
3726 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3727 let cache = guard
3728 .as_ref()
3729 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3730 let scope = match &spec.call {
3737 None => WhereScope::table(
3738 &spec.schema_name,
3739 &spec.table_name,
3740 &spec.type_name,
3741 spec.names.as_ref(),
3742 )
3743 .with_resolution(cache, spec.relationships.as_ref()),
3744 Some(_) => WhereScope::for_alias(
3745 &spec.schema_name,
3746 &spec.table_name,
3747 &spec.table_name,
3748 &spec.type_name,
3749 cache,
3750 spec.relationships.as_ref(),
3751 spec.names.as_ref(),
3752 ),
3753 }
3754 .for_caller(gql_ctx.caller());
3755 let (sql, values) =
3756 build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3757 if !sql.is_empty() {
3758 where_sql = format!(" {}", sql);
3759 bound_values.extend(values);
3760 }
3761 }
3762 }
3763
3764 let order_sql = build_order_by_clause(
3765 ctx,
3766 &gql_ctx.schema_cache,
3767 &spec.schema_name,
3768 &spec.table_name,
3769 &spec.type_name,
3770 spec.relationships.as_ref(),
3771 &format!(
3772 "{}.{}",
3773 postrust_sql::escape_ident(&spec.schema_name),
3774 postrust_sql::escape_ident(&spec.table_name)
3775 ),
3776 spec.names.as_ref(),
3777 )
3778 .await?;
3779
3780 let requested_limit = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
3781 let offset = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
3782 let ceiling = match (
3783 spec.max_rows,
3784 permission_limit(
3785 &gql_ctx.caller(),
3786 spec.names.as_ref(),
3787 &spec.schema_name,
3788 &spec.table_name,
3789 ),
3790 ) {
3791 (Some(configured), Some(granted)) => Some(configured.min(granted)),
3792 (only, None) | (None, only) => only,
3793 };
3794 let limit = match (requested_limit, ceiling) {
3795 (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
3796 (Some(requested), None) => Some(requested),
3797 (None, ceiling) => ceiling,
3798 };
3799
3800 let rows = format!("SELECT * FROM {}{}{}", source, where_sql, order_sql);
3801
3802 let mut inner = rows.clone();
3804 if let Some(limit) = limit {
3805 inner.push_str(&format!(" LIMIT {}", limit));
3806 }
3807 if let Some(offset) = offset {
3808 inner.push_str(&format!(" OFFSET {}", offset));
3809 }
3810
3811 let mut counted = rows;
3825 if let Some(limit) = requested_limit {
3826 counted.push_str(&format!(" LIMIT {}", limit));
3827 }
3828 if let Some(offset) = offset {
3829 counted.push_str(&format!(" OFFSET {}", offset));
3830 }
3831
3832 let mut wants_nodes = false;
3837 let mut wants_aggregate = false;
3838 let mut wanted: BTreeMap<String, Vec<String>> = BTreeMap::new();
3839 let mut counts: Vec<(String, String)> = Vec::new();
3842 for selection in ctx.field().selection_set() {
3843 match selection.name() {
3844 "nodes" => wants_nodes = true,
3845 "aggregate" => {
3846 wants_aggregate = true;
3847 for function in selection.selection_set() {
3848 if function.name() == "__typename" {
3850 continue;
3851 }
3852 if function.name() == "count" {
3853 counts.push((
3854 function.alias().unwrap_or("count").to_string(),
3855 count_expression(
3856 function,
3857 (&spec.schema_name, &spec.table_name),
3858 None,
3859 spec.names.as_ref(),
3860 ),
3861 ));
3862 continue;
3863 }
3864 let columns = wanted.entry(function.name().to_string()).or_default();
3867 for column in function.selection_set() {
3868 let name = column.name().to_string();
3869 if name != "__typename" && !columns.contains(&name) {
3870 columns.push(name);
3871 }
3872 }
3873 }
3874 }
3875 _ => {}
3876 }
3877 }
3878
3879 let mut result = async_graphql::indexmap::IndexMap::new();
3880
3881 if wants_aggregate {
3884 let mut parts = match counts.iter().any(|(key, _)| key == "count") {
3888 true => Vec::new(),
3889 false => vec!["'count', count(*)".to_string()],
3890 };
3891 for (key, expression) in &counts {
3892 parts.push(format!(
3893 "{}, {}",
3894 postrust_sql::quote_literal(key),
3895 expression
3896 ));
3897 }
3898 for (function, columns) in &wanted {
3899 let sql_function = agg::NUMERIC_AGGREGATES
3903 .iter()
3904 .chain(agg::ORDERED_AGGREGATES.iter())
3905 .find(|(name, _)| name == function)
3906 .map(|(name, _)| *name)
3907 .ok_or_else(|| {
3908 async_graphql::Error::new(format!("unknown aggregate \"{}\"", function))
3909 })?;
3910 let per_column: Vec<String> = columns
3911 .iter()
3912 .map(|field| {
3913 let source = spec
3914 .names
3915 .column_source(&spec.schema_name, &spec.table_name, field)
3916 .unwrap_or(field);
3917 format!(
3918 "{}, {}({})",
3919 postrust_sql::quote_literal(field),
3920 sql_function,
3921 postrust_sql::escape_ident(source)
3922 )
3923 })
3924 .collect();
3925 parts.push(format!(
3926 "'{}', json_build_object({})",
3927 sql_function,
3928 per_column.join(", ")
3929 ));
3930 }
3931
3932 let sql = format!(
3933 "SELECT json_build_object({}) FROM ({}) AS pgrst_agg",
3934 parts.join(", "),
3935 counted
3936 );
3937 let mut conn = begin_with_session(pool, gql_ctx).await?;
3938 let rows = execute_query_on(&mut conn, &sql, &bound_values).await?;
3939 conn.commit().await?;
3940 if let Some(first) = rows.into_iter().next() {
3941 if let Value::Object(map) = json_to_value(first) {
3942 result.insert(async_graphql::Name::new("aggregate"), Value::Object(map));
3943 }
3944 }
3945 }
3946
3947 if wants_nodes {
3948 let nodes = ctx
3954 .field()
3955 .selection_set()
3956 .find(|selection| selection.name() == "nodes")
3957 .ok_or_else(|| async_graphql::Error::new("nodes was asked for and then was not"))?;
3958
3959 let mut node_values = bound_values.clone();
3963 let (projection, row_column_types) = {
3964 let guard = gql_ctx
3965 .schema_cache
3966 .get()
3967 .await
3968 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3969 let cache = guard
3970 .as_ref()
3971 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3972 let qi = postrust_core::api_request::QualifiedIdentifier::new(
3973 &spec.schema_name,
3974 &spec.table_name,
3975 );
3976 let table = cache.get_table(&qi);
3977 let mut param_idx = node_values.len() + 1;
3978 let computed = match table {
3979 Some(table) => computed_projections(
3980 table,
3981 nodes.selection_set(),
3982 "src",
3983 spec.names.as_ref(),
3984 cache,
3985 &mut param_idx,
3986 &mut node_values,
3987 )?,
3988 None => Vec::new(),
3989 };
3990 let embeds = build_embed_expressions(
3991 &gql_ctx.caller(),
3992 cache,
3993 spec.relationships.as_ref(),
3994 &spec.type_name,
3995 "src",
3996 nodes.selection_set(),
3997 spec.max_rows,
3998 &mut 0,
3999 &mut param_idx,
4000 &mut node_values,
4001 spec.names.as_ref(),
4002 )?;
4003 let mut projection = table
4007 .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
4008 .unwrap_or_else(|| "src.*".to_string());
4009 for expression in &computed {
4010 projection.push_str(", ");
4011 projection.push_str(expression);
4012 }
4013 for (field_name, expression) in &embeds {
4014 projection.push_str(", ");
4015 projection.push_str(expression);
4016 projection.push_str(" AS ");
4017 projection.push_str(&postrust_sql::escape_ident(field_name));
4018 }
4019 (
4020 projection,
4021 table
4022 .map(|table| exposed_column_types(table, spec.names.as_ref()))
4023 .unwrap_or_default(),
4024 )
4025 };
4026
4027 let sql = format!(
4028 "SELECT {} FROM (SELECT {} FROM ({}) AS src) AS pgrst_nodes",
4029 row_json("pgrst_nodes", &row_column_types),
4030 projection,
4031 inner
4032 );
4033 let mut conn = begin_with_session(pool, gql_ctx).await?;
4034 let rows = execute_query_on(&mut conn, &sql, &node_values).await?;
4035 conn.commit().await?;
4036 result.insert(
4037 async_graphql::Name::new("nodes"),
4038 Value::List(rows.into_iter().map(json_to_value).collect()),
4039 );
4040 }
4041
4042 let _ = &spec.type_name;
4043 Ok(Value::Object(result))
4044}
4045
4046async fn resolve_query<'a>(
4048 ctx: &ResolverContext<'a>,
4049 spec: &QueryFieldSpec,
4050) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4051 Ok(rows_as_field_value(
4052 query_rows(ctx, spec).await?,
4053 spec.is_by_pk,
4054 ))
4055}
4056
4057async fn query_rows(
4063 ctx: &ResolverContext<'_>,
4064 spec: &QueryFieldSpec,
4065) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4066 let schema_name = spec.schema_name.as_str();
4067 let table_name = spec.table_name.as_str();
4068 let type_name = spec.type_name.as_str();
4069 let is_by_pk = spec.is_by_pk;
4070 let pk_columns = spec.pk_columns.as_slice();
4071 let max_rows = spec.max_rows;
4072 let relationships = spec.relationships.as_ref();
4073
4074 let pool = ctx.data::<PgPool>()?;
4075 let gql_ctx = ctx.data::<GraphQLContext>()?;
4076
4077 debug!("Resolving query for table: {}", table_name);
4078
4079 let requested_limit: Option<i64> = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
4081
4082 let offset: Option<i64> = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
4083
4084 let limit: Option<i64> = if is_by_pk {
4089 Some(1)
4090 } else {
4091 let ceiling = match (
4095 max_rows,
4096 permission_limit(
4097 &gql_ctx.caller(),
4098 spec.names.as_ref(),
4099 schema_name,
4100 table_name,
4101 ),
4102 ) {
4103 (Some(configured), Some(granted)) => Some(configured.min(granted)),
4104 (only, None) | (None, only) => only,
4105 };
4106 match (requested_limit, ceiling) {
4107 (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
4108 (Some(requested), None) => Some(requested),
4109 (None, ceiling) => ceiling,
4110 }
4111 };
4112
4113 let mut where_sql = String::new();
4121 let mut bound_values: Vec<serde_json::Value> = Vec::new();
4122
4123 let source = match &spec.call {
4127 None => format!(
4133 "{}.{} AS {}",
4134 postrust_sql::escape_ident(schema_name),
4135 postrust_sql::escape_ident(table_name),
4136 postrust_sql::escape_ident(READ_ROW)
4137 ),
4138 Some(call) => function_source(ctx, call, table_name, &mut bound_values)?,
4139 };
4140
4141 let source_ref = match &spec.call {
4144 None => postrust_sql::escape_ident(READ_ROW),
4145 Some(_) => postrust_sql::escape_ident(table_name),
4146 };
4147
4148 if is_by_pk {
4149 if pk_columns.is_empty() {
4150 return Err(async_graphql::Error::new(format!(
4151 "\"{}\" has no primary key, so it cannot be queried by key",
4152 table_name
4153 )));
4154 }
4155
4156 let mut conditions = Vec::with_capacity(pk_columns.len());
4157 for (idx, (col_name, pg_type)) in pk_columns.iter().enumerate() {
4158 let field = spec
4159 .names
4160 .column(schema_name, table_name, col_name)
4161 .unwrap_or(col_name);
4162 let value = ctx.args.try_get(field).map_err(|_| {
4163 async_graphql::Error::new(format!(
4164 "missing required primary key argument \"{}\"",
4165 field
4166 ))
4167 })?;
4168
4169 conditions.push(format!(
4170 "{} = ${}::{}",
4171 postrust_sql::escape_ident(col_name),
4172 idx + 1,
4173 pg_type
4174 ));
4175 bound_values.push(accessor_to_json(&value));
4176 }
4177 where_sql = format!(" WHERE {}", conditions.join(" AND "));
4178 }
4179
4180 {
4185 let requested = match is_by_pk {
4186 true => None,
4187 false => ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v)),
4188 };
4189 let permission = permission_predicate(
4190 &gql_ctx.caller(),
4191 spec.names.as_ref(),
4192 schema_name,
4193 table_name,
4194 )?;
4195
4196 if let Some(predicate) = and_predicates(requested, permission) {
4197 let guard = gql_ctx
4198 .schema_cache
4199 .get()
4200 .await
4201 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4202 let cache = guard
4203 .as_ref()
4204 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4205 let scope = match &spec.call {
4206 None => WhereScope::table(schema_name, table_name, type_name, spec.names.as_ref())
4207 .under_alias(READ_ROW)
4208 .with_resolution(cache, relationships)
4209 .for_caller(gql_ctx.caller()),
4210 Some(_) => WhereScope::for_alias(
4213 schema_name,
4214 table_name,
4215 table_name,
4216 type_name,
4217 cache,
4218 relationships,
4219 spec.names.as_ref(),
4220 )
4221 .for_caller(gql_ctx.caller()),
4222 };
4223 let (filter_sql, filter_values) =
4224 build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
4225 if !filter_sql.is_empty() {
4226 where_sql = match where_sql.is_empty() {
4229 true => format!(" {}", filter_sql),
4230 false => format!(
4231 "{} AND ({})",
4232 where_sql,
4233 filter_sql.trim_start_matches("WHERE ")
4234 ),
4235 };
4236 bound_values.extend(filter_values);
4237 }
4238 }
4239 }
4240
4241 let (order_sql, distinct_on) = if is_by_pk {
4244 (String::new(), Vec::new())
4245 } else {
4246 (
4247 build_order_by_clause(
4248 ctx,
4249 &gql_ctx.schema_cache,
4250 schema_name,
4251 table_name,
4252 type_name,
4253 relationships,
4254 &source_ref,
4255 spec.names.as_ref(),
4256 )
4257 .await?,
4258 build_distinct_on(
4259 ctx,
4260 &gql_ctx.schema_cache,
4261 schema_name,
4262 table_name,
4263 spec.names.as_ref(),
4264 )
4265 .await?,
4266 )
4267 };
4268
4269 let (distinct_sql, order_sql) = {
4277 let written = order_sql.strip_prefix(" ORDER BY ");
4278 let (prefix, order) = distinct_on_clause(&distinct_on, written).map_err(|error| {
4281 at_path(
4282 error,
4283 &format!(
4284 "$.selectionSet.{}.args",
4285 ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4286 ),
4287 )
4288 })?;
4289 (
4290 prefix,
4291 match order {
4292 Some(order) => format!(" ORDER BY {}", order),
4293 None => String::new(),
4294 },
4295 )
4296 };
4297
4298 let (computed, row_column_types) = {
4301 let guard = gql_ctx
4302 .schema_cache
4303 .get()
4304 .await
4305 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4306 match guard.as_ref() {
4307 Some(cache) => {
4308 let qi =
4309 postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
4310 match cache.get_table(&qi) {
4311 Some(table) => {
4312 let mut param_idx = bound_values.len() + 1;
4313 (
4314 computed_projections(
4315 table,
4316 ctx.field().selection_set(),
4317 "src",
4318 spec.names.as_ref(),
4319 cache,
4320 &mut param_idx,
4321 &mut bound_values,
4322 )?,
4323 exposed_column_types(table, spec.names.as_ref()),
4324 )
4325 }
4326 None => (Vec::new(), HashMap::new()),
4327 }
4328 }
4329 None => (Vec::new(), HashMap::new()),
4330 }
4331 };
4332 let mut inner = format!(
4336 "SELECT {}* FROM {}{}{}",
4337 distinct_sql, source, where_sql, order_sql
4338 );
4339
4340 if let Some(limit) = limit {
4341 inner.push_str(&format!(" LIMIT {}", limit));
4342 }
4343
4344 if let Some(offset) = offset {
4345 inner.push_str(&format!(" OFFSET {}", offset));
4346 }
4347
4348 let embed_expressions = {
4351 let guard = gql_ctx
4352 .schema_cache
4353 .get()
4354 .await
4355 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4356 match guard.as_ref() {
4357 Some(cache) => {
4358 let mut param_idx = bound_values.len() + 1;
4363 build_embed_expressions(
4364 &gql_ctx.caller(),
4365 cache,
4366 relationships,
4367 type_name,
4368 "src",
4369 ctx.field().selection_set(),
4370 max_rows,
4371 &mut 0,
4372 &mut param_idx,
4373 &mut bound_values,
4374 spec.names.as_ref(),
4375 )?
4376 }
4377 None => Vec::new(),
4378 }
4379 };
4380
4381 let renamed = {
4389 let guard = gql_ctx
4390 .schema_cache
4391 .get()
4392 .await
4393 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4394 guard
4395 .as_ref()
4396 .and_then(|cache| {
4397 cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
4398 schema_name,
4399 table_name,
4400 ))
4401 })
4402 .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
4403 };
4404 let inner = if embed_expressions.is_empty() && computed.is_empty() && renamed.is_none() {
4405 inner
4406 } else {
4407 let mut projection = renamed.unwrap_or_else(|| "src.*".to_string());
4408 for expression in &computed {
4409 projection.push_str(", ");
4410 projection.push_str(expression);
4411 }
4412 for (field_name, expression) in &embed_expressions {
4413 projection.push_str(", ");
4414 projection.push_str(expression);
4415 projection.push_str(" AS ");
4416 projection.push_str(&postrust_sql::escape_ident(field_name));
4417 }
4418 format!("SELECT {} FROM ({}) AS src", projection, inner)
4419 };
4420
4421 let sql = format!(
4422 "SELECT {} FROM ({}) t",
4423 row_json("t", &row_column_types),
4424 inner
4425 );
4426
4427 let mut tx = begin_with_session(pool, gql_ctx).await?;
4431
4432 let mut result = execute_query_on(&mut tx, &sql, &bound_values).await?;
4434
4435 if embed_expressions.is_empty() {
4437 let guard = gql_ctx
4438 .schema_cache
4439 .get()
4440 .await
4441 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4442 let cache = guard
4443 .as_ref()
4444 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4445
4446 let embed_ctx = EmbedContext {
4447 schema_cache: cache,
4448 relationships,
4449 max_rows,
4450 };
4451
4452 embed_relationships(&mut tx, &embed_ctx, type_name, ctx.field(), &mut result).await?;
4453 }
4454
4455 tx.commit().await?;
4456
4457 Ok(result)
4458}
4459
4460fn rows_as_field_value<'a>(rows: Vec<serde_json::Value>, is_by_pk: bool) -> Option<FieldValue<'a>> {
4465 match is_by_pk {
4466 true => rows
4467 .into_iter()
4468 .next()
4469 .map(|v| FieldValue::value(json_to_value(v))),
4470 false => Some(FieldValue::list(
4471 rows.into_iter()
4472 .map(|v| FieldValue::value(json_to_value(v))),
4473 )),
4474 }
4475}
4476
4477#[allow(clippy::too_many_arguments)]
4479async fn resolve_mutation<'a>(
4480 ctx: &ResolverContext<'a>,
4481 schema_name: &str,
4482 table_name: &str,
4483 mutation_type: MutationType,
4484 pk_columns: &[(String, String)],
4485 relationships: &Arc<HashMap<String, Vec<RelationshipField>>>,
4486 type_names: &Arc<HashMap<(String, String), String>>,
4487 names: &Arc<crate::names::NameOverrides>,
4488 max_rows: Option<i64>,
4489) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4490 let pool = ctx.data::<PgPool>()?;
4491 let gql_ctx = ctx.data::<GraphQLContext>()?;
4492
4493 debug!(
4494 "Resolving mutation for table: {} type: {:?}",
4495 table_name, mutation_type
4496 );
4497
4498 let type_name = type_names
4504 .get(&(schema_name.to_string(), table_name.to_string()))
4505 .cloned()
4506 .unwrap_or_else(|| table_name.to_string());
4507
4508 let by_key = matches!(
4509 mutation_type,
4510 MutationType::InsertOne | MutationType::UpdateByPk | MutationType::DeleteByPk
4511 );
4512 let returning = if by_key {
4513 Some(ctx.field())
4514 } else {
4515 ctx.field()
4516 .selection_set()
4517 .find(|selection| selection.name() == "returning")
4518 };
4519
4520 if mutation_type == MutationType::UpdateMany {
4525 let updates = ctx
4526 .args
4527 .try_get("updates")
4528 .ok()
4529 .map(|v| accessor_to_json(&v))
4530 .unwrap_or(serde_json::Value::Null);
4531 let serde_json::Value::Array(entries) = updates else {
4532 return Err(async_graphql::Error::new(
4533 "\"updates\" takes a list of {where, _set, …} objects",
4534 ));
4535 };
4536 let column_types = column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await;
4537 let mut answers: Vec<FieldValue<'_>> = Vec::with_capacity(entries.len());
4538 for entry in entries {
4539 let serde_json::Value::Object(entry) = entry else {
4540 return Err(async_graphql::Error::new(
4541 "each update is an object of {where, _set, …}",
4542 ));
4543 };
4544 let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4545 .iter()
4546 .filter_map(|name| entry.get(*name).map(|value| (*name, value.clone())))
4547 .filter(|(_, value)| !value.is_null())
4548 .collect();
4549 let rows = execute_update(
4550 pool,
4551 gql_ctx,
4552 schema_name,
4553 table_name,
4554 &type_name,
4555 operators,
4556 column_types.clone(),
4557 entry.get("where").cloned(),
4558 relationships.as_ref(),
4559 names.as_ref(),
4560 )
4561 .await?;
4562 let affected = rows.len();
4563 let rows = match returning {
4564 Some(returning) if !rows.is_empty() => {
4565 reread_returning(
4566 pool,
4567 gql_ctx,
4568 schema_name,
4569 table_name,
4570 &type_name,
4571 rows,
4572 returning,
4573 relationships.as_ref(),
4574 names.as_ref(),
4575 max_rows,
4576 )
4577 .await?
4578 }
4579 _ => rows,
4580 };
4581 answers.extend(mutation_result(rows, affected, false));
4582 }
4583 return Ok(Some(FieldValue::list(answers)));
4584 }
4585
4586 let (result, affected) = match mutation_type {
4587 MutationType::Insert | MutationType::InsertOne => {
4588 let objects = ctx
4589 .args
4590 .try_get("objects")
4591 .ok()
4592 .map(|v| accessor_to_json(&v))
4593 .or_else(|| {
4594 ctx.args
4597 .try_get("object")
4598 .ok()
4599 .map(|v| serde_json::Value::Array(vec![accessor_to_json(&v)]))
4600 })
4601 .unwrap_or_else(|| serde_json::Value::Array(vec![]));
4602
4603 let guard = gql_ctx
4604 .schema_cache
4605 .get()
4606 .await
4607 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4608 let cache = guard
4609 .as_ref()
4610 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4611 let here = format!(
4615 "$.selectionSet.{}",
4616 ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4617 );
4618 let named = match mutation_type {
4621 MutationType::InsertOne => "object",
4622 _ => "objects",
4623 };
4624 let context = InsertContext {
4625 on_conflict: ctx
4626 .args
4627 .try_get("on_conflict")
4628 .ok()
4629 .map(|v| accessor_to_json(&v))
4630 .filter(|v| !v.is_null()),
4631 cache,
4632 relationships: relationships.as_ref(),
4633 type_names: type_names.as_ref(),
4634 names: names.as_ref(),
4635 caller: gql_ctx.caller(),
4636 row: String::new(),
4639 objects: format!("{}.args.{}", here, named),
4640 conflict: format!("{}.args.on_conflict", here),
4641 };
4642
4643 execute_insert(
4644 pool,
4645 gql_ctx,
4646 schema_name,
4647 table_name,
4648 objects,
4649 &context,
4650 mutation_type == MutationType::InsertOne,
4651 )
4652 .await?
4653 }
4654 MutationType::UpdateMany | MutationType::Update | MutationType::UpdateByPk => {
4657 let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4661 .iter()
4662 .filter_map(|name| {
4663 ctx.args
4664 .try_get(name)
4665 .ok()
4666 .map(|v| (*name, accessor_to_json(&v)))
4667 })
4668 .filter(|(_, value)| !value.is_null())
4669 .collect();
4670
4671 let where_clause = if mutation_type == MutationType::UpdateByPk {
4672 Some(pk_where_from_args(
4673 ctx,
4674 schema_name,
4675 table_name,
4676 pk_columns,
4677 names.as_ref(),
4678 )?)
4679 } else {
4680 ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4681 };
4682 let where_clause = and_predicates(
4686 where_clause,
4687 permission_filter(
4688 &gql_ctx.caller(),
4689 names.as_ref(),
4690 schema_name,
4691 table_name,
4692 crate::role::Verb::Update,
4693 )?,
4694 );
4695
4696 execute_update(
4697 pool,
4698 gql_ctx,
4699 schema_name,
4700 table_name,
4701 &type_name,
4702 operators,
4703 column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await,
4704 where_clause,
4705 relationships.as_ref(),
4706 names.as_ref(),
4707 )
4708 .await
4709 .map(|rows| {
4710 let count = rows.len();
4711 (rows, count)
4712 })?
4713 }
4714 MutationType::Delete | MutationType::DeleteByPk => {
4715 let where_clause = if mutation_type == MutationType::DeleteByPk {
4716 Some(pk_where_from_args(
4717 ctx,
4718 schema_name,
4719 table_name,
4720 pk_columns,
4721 names.as_ref(),
4722 )?)
4723 } else {
4724 ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4725 };
4726 let where_clause = and_predicates(
4727 where_clause,
4728 permission_filter(
4729 &gql_ctx.caller(),
4730 names.as_ref(),
4731 schema_name,
4732 table_name,
4733 crate::role::Verb::Delete,
4734 )?,
4735 );
4736
4737 execute_delete(
4738 pool,
4739 gql_ctx,
4740 schema_name,
4741 table_name,
4742 &type_name,
4743 where_clause,
4744 returning,
4745 relationships.as_ref(),
4746 names.as_ref(),
4747 max_rows,
4748 )
4749 .await
4750 .map(|rows| {
4751 let count = rows.len();
4752 (rows, count)
4753 })?
4754 }
4755 };
4756
4757 let is_delete = matches!(
4758 mutation_type,
4759 MutationType::Delete | MutationType::DeleteByPk
4760 );
4761 let result = match returning {
4762 Some(returning) if !result.is_empty() && !is_delete => {
4763 reread_returning(
4764 pool,
4765 gql_ctx,
4766 schema_name,
4767 table_name,
4768 &type_name,
4769 result,
4770 returning,
4771 relationships.as_ref(),
4772 names.as_ref(),
4773 max_rows,
4774 )
4775 .await?
4776 }
4777 _ => result,
4778 };
4779
4780 Ok(mutation_result(result, affected, by_key))
4781}
4782
4783async fn write_tx<'a>(
4791 gql_ctx: &'a GraphQLContext,
4792 pool: &PgPool,
4793) -> Result<
4794 tokio::sync::MutexGuard<'a, Option<sqlx::Transaction<'static, sqlx::Postgres>>>,
4795 async_graphql::Error,
4796> {
4797 let mut guard = gql_ctx.write.lock().await;
4798 if guard.is_none() {
4799 *guard = Some(begin_with_session(pool, gql_ctx).await?);
4800 }
4801 Ok(guard)
4802}
4803
4804async fn begin_with_session(
4821 pool: &PgPool,
4822 gql_ctx: &GraphQLContext,
4823) -> Result<sqlx::Transaction<'static, sqlx::Postgres>, async_graphql::Error> {
4824 let mut tx = pool.begin().await?;
4825 gql_ctx.session().apply_sqlx(&mut tx).await?;
4826 Ok(tx)
4827}
4828
4829fn row_json(expr: &str, column_types: &HashMap<String, String>) -> String {
4840 let mut shapes: Vec<(&String, &String)> = column_types
4841 .iter()
4842 .filter(|(_, pg_type)| matches!(pg_type.as_str(), "geometry" | "geography"))
4843 .collect();
4844 if shapes.is_empty() {
4845 return format!("row_to_json({})", expr);
4846 }
4847 shapes.sort();
4848
4849 let overrides: Vec<String> = shapes
4850 .iter()
4851 .map(|(name, _)| {
4852 format!(
4853 "{}, ST_AsGeoJSON({}.{}, 9, 4)::jsonb",
4859 postrust_sql::quote_literal(name),
4860 expr,
4861 postrust_sql::escape_ident(name)
4862 )
4863 })
4864 .collect();
4865 format!(
4866 "(to_jsonb({}) || jsonb_build_object({}))",
4867 expr,
4868 overrides.join(", ")
4869 )
4870}
4871
4872async fn execute_query_on(
4875 conn: &mut sqlx::PgConnection,
4876 sql: &str,
4877 params: &[serde_json::Value],
4878) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4879 use sqlx::Row;
4880
4881 trace!("Executing SQL: {}", sql);
4882
4883 let mut query = sqlx::query(AssertSqlSafe(sql));
4885 for param in params {
4886 query = bind_json_value(query, param);
4887 }
4888 let rows = query.fetch_all(&mut *conn).await.map_err(database_error)?;
4889
4890 let results: Vec<serde_json::Value> = rows
4893 .into_iter()
4894 .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
4895 .collect();
4896
4897 Ok(results)
4898}
4899
4900fn check_geojson(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4913 fn refuse<T>(message: &str) -> Result<T, async_graphql::Error> {
4914 Err(coded_error("parse-failed", message))
4915 }
4916
4917 fn position(
4918 value: &serde_json::Value,
4919 ) -> Result<&Vec<serde_json::Value>, async_graphql::Error> {
4920 match value.as_array() {
4921 Some(items) if items.len() >= 2 && items.iter().all(|i| i.is_number()) => Ok(items),
4922 _ => refuse("A Position needs at least 2 elements"),
4923 }
4924 }
4925
4926 fn positions(
4927 value: Option<&serde_json::Value>,
4928 least: usize,
4929 what: &str,
4930 ) -> Result<Vec<Vec<serde_json::Value>>, async_graphql::Error> {
4931 let items = match value.and_then(|v| v.as_array()) {
4932 Some(items) => items,
4933 None => return refuse(&format!("A {} needs at least {} Positions", what, least)),
4934 };
4935 if items.len() < least {
4936 return refuse(&format!("A {} needs at least {} Positions", what, least));
4937 }
4938 items.iter().map(|item| position(item).cloned()).collect()
4939 }
4940
4941 fn ring(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4942 let points = positions(Some(value), 4, "LinearRing")?;
4943 match (points.first(), points.last()) {
4944 (Some(first), Some(last)) if first == last => Ok(()),
4945 _ => refuse("the first and last locations have to be equal for a LinearRing"),
4946 }
4947 }
4948
4949 fn polygon(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4950 let Some(rings) = value.as_array() else {
4951 return refuse("A LinearRing needs at least 4 Positions");
4952 };
4953 rings.iter().try_for_each(ring)
4954 }
4955
4956 let serde_json::Value::Object(map) = value else {
4957 return Ok(());
4958 };
4959 let Some(kind) = map.get("type").and_then(|v| v.as_str()) else {
4960 return Ok(());
4961 };
4962 let coordinates = map.get("coordinates");
4963 match kind {
4964 "Point" => {
4965 position(coordinates.unwrap_or(&serde_json::Value::Null))?;
4966 }
4967 "MultiPoint" => {
4968 positions(coordinates, 0, "MultiPoint")?;
4969 }
4970 "LineString" => {
4971 positions(coordinates, 2, "LineString")?;
4972 }
4973 "MultiLineString" => {
4974 for line in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4975 positions(Some(line), 2, "LineString")?;
4976 }
4977 }
4978 "Polygon" => polygon(coordinates.unwrap_or(&serde_json::Value::Null))?,
4979 "MultiPolygon" => {
4980 for one in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4981 polygon(one)?;
4982 }
4983 }
4984 "GeometryCollection" => {
4985 for geometry in map
4986 .get("geometries")
4987 .and_then(|v| v.as_array())
4988 .into_iter()
4989 .flatten()
4990 {
4991 check_geojson(geometry)?;
4992 }
4993 }
4994 other => return refuse(&format!("unexpected geometry type: {}", other)),
4995 }
4996 Ok(())
4997}
4998
4999fn computed_call(
5007 definition: &postrust_core::schema_cache::ComputedColumn,
5008 row: &str,
5009 row_by_name: &str,
5010) -> String {
5011 let function = format!(
5012 "{}.{}",
5013 postrust_sql::escape_ident(&definition.function.schema),
5014 postrust_sql::escape_ident(&definition.function.name)
5015 );
5016 match (&definition.session_argument, &definition.row_argument) {
5017 (Some(session), Some(row_argument)) => format!(
5018 "{}({} => {}, {} => {})",
5019 function,
5020 postrust_sql::escape_ident(row_argument),
5021 row_by_name,
5022 postrust_sql::escape_ident(session),
5023 SESSION_ARGUMENT
5024 ),
5025 _ => format!("{}({})", function, row),
5026 }
5027}
5028
5029const SESSION_ARGUMENT: &str = "coalesce(current_setting('hasura.session', true), '{}')::json";
5036
5037const UPDATE_OPERATORS: [&str; 7] = [
5040 "_set",
5041 "_inc",
5042 "_append",
5043 "_prepend",
5044 "_delete_key",
5045 "_delete_elem",
5046 "_delete_at_path",
5047];
5048
5049const READ_ROW: &str = "pgrst_src";
5055
5056const WRITTEN_ROW: &str = "pgrst_row";
5061
5062fn write_cast(column_types: &HashMap<String, String>, column: &str) -> String {
5070 match column_types.get(column) {
5071 Some(pg_type) => format!("::{}", pg_type),
5072 None => String::new(),
5073 }
5074}
5075
5076fn write_operand(
5083 column_types: &HashMap<String, String>,
5084 column: &str,
5085 value: &serde_json::Value,
5086) -> serde_json::Value {
5087 match column_types.get(column).map(String::as_str) {
5088 Some("json") | Some("jsonb") => match value {
5089 serde_json::Value::String(_)
5090 | serde_json::Value::Number(_)
5091 | serde_json::Value::Bool(_) => serde_json::Value::String(value.to_string()),
5092 other => other.clone(),
5093 },
5094 Some(pg_type) if is_array_type(pg_type) => match value {
5099 serde_json::Value::Array(_) => serde_json::Value::String(array_literal(value)),
5100 other => other.clone(),
5101 },
5102 _ => value.clone(),
5103 }
5104}
5105
5106fn is_array_type(pg_type: &str) -> bool {
5111 pg_type.ends_with("[]") || pg_type.starts_with('_')
5112}
5113
5114fn array_literal(value: &serde_json::Value) -> String {
5120 fn element(value: &serde_json::Value) -> String {
5121 match value {
5122 serde_json::Value::Null => "NULL".to_string(),
5123 serde_json::Value::Array(_) => array_literal(value),
5124 serde_json::Value::String(text) => {
5125 format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
5126 }
5127 other => format!("\"{}\"", other.to_string().replace('"', "\\\"")),
5128 }
5129 }
5130 let items = match value {
5131 serde_json::Value::Array(items) => items,
5132 other => return element(other),
5133 };
5134 format!(
5135 "{{{}}}",
5136 items.iter().map(element).collect::<Vec<_>>().join(",")
5137 )
5138}
5139
5140fn write_expression(
5151 column_types: &HashMap<String, String>,
5152 column: &str,
5153 value: &serde_json::Value,
5154 placeholder: &str,
5155) -> String {
5156 let pg_type = column_types.get(column).map(String::as_str);
5157 if value.is_object() && matches!(pg_type, Some("geometry") | Some("geography")) {
5158 return match pg_type {
5159 Some("geography") => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
5160 _ => format!("ST_GeomFromGeoJSON({})", placeholder),
5161 };
5162 }
5163 if pg_type == Some("money") && value.is_number() {
5169 return format!("{}::numeric::money", placeholder);
5170 }
5171 format!("{}{}", placeholder, write_cast(column_types, column))
5172}
5173
5174async fn column_types_of(
5176 schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
5177 schema_name: &str,
5178 table_name: &str,
5179) -> HashMap<String, String> {
5180 let Ok(guard) = schema_cache.get().await else {
5181 return HashMap::new();
5182 };
5183 let Some(cache) = guard.as_ref() else {
5184 return HashMap::new();
5185 };
5186 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5187 match cache.get_table(&qi) {
5188 Some(table) => table
5189 .columns
5190 .values()
5191 .map(|c| (c.name.clone(), c.nominal_type.clone()))
5192 .collect(),
5193 None => HashMap::new(),
5194 }
5195}
5196
5197fn insert_row<'life>(
5215 conn: &'life mut sqlx::PgConnection,
5216 schema_name: &'life str,
5217 table_name: &'life str,
5218 object: serde_json::Map<String, serde_json::Value>,
5219 context: &'life InsertContext<'life>,
5220 written_count: &'life mut usize,
5221) -> std::pin::Pin<
5222 Box<
5223 dyn std::future::Future<Output = Result<serde_json::Value, async_graphql::Error>>
5224 + Send
5225 + 'life,
5226 >,
5227> {
5228 Box::pin(async move {
5229 use sqlx::Row;
5230
5231 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5232 let table = context.cache.get_table(&qi).ok_or_else(|| {
5233 async_graphql::Error::new(format!("unknown table \"{}\"", table_name))
5234 })?;
5235 let column_types: HashMap<String, String> = table
5236 .columns
5237 .values()
5238 .map(|c| (c.name.clone(), c.nominal_type.clone()))
5239 .collect();
5240
5241 let type_name = context
5243 .type_names
5244 .get(&(schema_name.to_string(), table_name.to_string()));
5245 let relationships: &[RelationshipField] = type_name
5246 .and_then(|name| context.relationships.get(name))
5247 .map(|r| r.as_slice())
5248 .unwrap_or(&[]);
5249
5250 let mut columns = serde_json::Map::new();
5251 type Nested<'r> = (
5252 &'r RelationshipField,
5253 serde_json::Value,
5254 Option<serde_json::Value>,
5255 );
5256 let mut to_one: Vec<Nested> = Vec::new();
5257 let mut to_many: Vec<Nested> = Vec::new();
5258
5259 for (key, value) in object {
5260 match relationships.iter().find(|r| r.name == key) {
5261 None => {
5262 columns.insert(
5265 table_column_for(context.names, table, &key).to_string(),
5266 value,
5267 );
5268 }
5269 Some(_) if value.is_null() => {
5270 continue;
5274 }
5275 Some(relationship) => {
5276 let (data, conflict) = match &value {
5280 serde_json::Value::Object(map) => (
5281 map.get("data").cloned().unwrap_or(value.clone()),
5282 map.get("on_conflict").cloned().filter(|v| !v.is_null()),
5283 ),
5284 other => (other.clone(), None),
5285 };
5286 if child_holds_the_key(&relationship.relationship) {
5293 to_many.push((relationship, data, conflict));
5294 } else {
5295 to_one.push((relationship, data, conflict));
5296 }
5297 }
5298 }
5299 }
5300
5301 for (column, value) in &columns {
5304 if matches!(
5305 column_types.get(column).map(String::as_str),
5306 Some("geometry") | Some("geography")
5307 ) {
5308 check_geojson(value).map_err(|error| {
5311 let field = context
5312 .names
5313 .column(schema_name, table_name, column)
5314 .unwrap_or(column);
5315 at_path(error, &format!("{}.{}", context.row, field))
5316 })?;
5317 }
5318 }
5319
5320 for (relationship, data, conflict) in to_one {
5323 let plan =
5324 postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5325 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5326 let serde_json::Value::Object(child) = data else {
5327 return Err(async_graphql::Error::new(format!(
5328 "\"{}\" takes an object to insert",
5329 relationship.name
5330 )));
5331 };
5332 for (local, _) in &plan.columns {
5336 if columns.get(local).is_some_and(|v| !v.is_null()) {
5337 return Err(at_path(
5338 validation_error(format!(
5339 "cannot insert object relationship \"{}\" as \"{}\" column \
5340 values are already determined",
5341 relationship.name, local
5342 )),
5343 &format!("{}.{}", context.row, relationship.name),
5344 ));
5345 }
5346 }
5347 let under = format!("{}.{}", context.row, relationship.name);
5350 let nested = InsertContext {
5351 on_conflict: conflict,
5352 cache: context.cache,
5353 relationships: context.relationships,
5354 type_names: context.type_names,
5355 names: context.names,
5356 caller: context.caller,
5357 row: format!("{}.data", under),
5358 objects: format!("{}.data", under),
5359 conflict: format!("{}.on_conflict", under),
5360 };
5361 let written = insert_row(
5362 conn,
5363 &plan.foreign_schema,
5364 &plan.foreign_table,
5365 child,
5366 &nested,
5367 written_count,
5368 )
5369 .await?;
5370 for (local, foreign) in &plan.columns {
5371 if let Some(value) = written.get(foreign) {
5372 columns.insert(local.clone(), value.clone());
5373 }
5374 }
5375 }
5376
5377 for (column, value) in crate::role::presets(
5383 &context.caller,
5384 context.names,
5385 schema_name,
5386 table_name,
5387 crate::role::Verb::Insert,
5388 )
5389 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?
5390 {
5391 columns.insert(column, value);
5392 }
5393
5394 let column_names: Vec<&str> = columns.keys().map(|k| k.as_str()).collect();
5400 let mut conflict_values: Vec<serde_json::Value> = Vec::new();
5401 let conflict_sql = match &context.on_conflict {
5402 Some(serde_json::Value::Object(spec)) => {
5403 let constraint = spec.get("constraint").and_then(|v| v.as_str());
5404 let Some(constraint) = constraint else {
5405 return Err(async_graphql::Error::new(
5406 "on_conflict needs a constraint to resolve against",
5407 ));
5408 };
5409 if !table
5410 .unique_constraints
5411 .iter()
5412 .any(|(name, _)| name == constraint)
5413 {
5414 return Err(async_graphql::Error::new(format!(
5415 "\"{}\" is not a unique constraint of \"{}\"",
5416 constraint, table_name
5417 )));
5418 }
5419 let updates: Vec<&str> = spec
5420 .get("update_columns")
5421 .and_then(|v| v.as_array())
5422 .map(|items| items.iter().filter_map(|i| i.as_str()).collect())
5423 .unwrap_or_default();
5424 if updates.contains(&PLACEHOLDER_COLUMN) {
5428 return Err(at_path(
5429 coded_error("validation-failed", "erroneous column name"),
5430 &context.conflict,
5431 ));
5432 }
5433
5434 if updates.is_empty() {
5435 format!(
5436 " ON CONFLICT ON CONSTRAINT {} DO NOTHING",
5437 postrust_sql::escape_ident(constraint)
5438 )
5439 } else {
5440 let preset = crate::role::presets(
5446 &context.caller,
5447 context.names,
5448 schema_name,
5449 table_name,
5450 crate::role::Verb::Update,
5451 )
5452 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5453
5454 let mut param_idx = column_names.len() + 1;
5458 let mut assignments: Vec<String> =
5459 Vec::with_capacity(preset.len() + updates.len());
5460 for (column, value) in &preset {
5461 assignments.push(format!(
5462 "{} = {}",
5463 postrust_sql::escape_ident(column),
5464 write_expression(
5465 &column_types,
5466 column,
5467 value,
5468 &format!("${}", param_idx)
5469 )
5470 ));
5471 conflict_values.push(write_operand(&column_types, column, value));
5472 param_idx += 1;
5473 }
5474 for field in &updates {
5475 let column = table_column_for(context.names, table, field);
5476 if preset.iter().any(|(written, _)| written == column) {
5477 continue;
5478 }
5479 assignments.push(format!(
5480 "{} = EXCLUDED.{}",
5481 postrust_sql::escape_ident(column),
5482 postrust_sql::escape_ident(column)
5483 ));
5484 }
5485 let granted = permission_filter(
5498 &context.caller,
5499 context.names,
5500 schema_name,
5501 table_name,
5502 crate::role::Verb::Update,
5503 )?;
5504 let requested = spec.get("where").filter(|f| !f.is_null()).cloned();
5505 let condition = match and_predicates(requested, granted) {
5506 Some(filter) => {
5507 let filter = &filter;
5508 let type_name = context
5509 .type_names
5510 .get(&(schema_name.to_string(), table_name.to_string()))
5511 .map(String::as_str)
5512 .unwrap_or(table_name);
5513 let scope = WhereScope::table(
5514 schema_name,
5515 table_name,
5516 type_name,
5517 context.names,
5518 )
5519 .under_alias(WRITTEN_ROW)
5520 .with_resolution(context.cache, context.relationships);
5521 let mut alias_counter = 0usize;
5522 build_condition(
5523 filter,
5524 &scope,
5525 &mut param_idx,
5526 &mut conflict_values,
5527 &mut alias_counter,
5528 )?
5529 .map(|sql| format!(" WHERE {}", sql))
5530 .unwrap_or_default()
5531 }
5532 None => String::new(),
5533 };
5534 format!(
5535 " ON CONFLICT ON CONSTRAINT {} DO UPDATE SET {}{}",
5536 postrust_sql::escape_ident(constraint),
5537 assignments.join(", "),
5538 condition
5539 )
5540 }
5541 }
5542 _ => String::new(),
5543 };
5544
5545 let qualified_table = postrust_sql::escape_ident(WRITTEN_ROW);
5551
5552 let check_scope = WhereScope::for_alias(
5557 schema_name,
5558 table_name,
5559 WRITTEN_ROW,
5560 context
5561 .type_names
5562 .get(&(schema_name.to_string(), table_name.to_string()))
5563 .map(String::as_str)
5564 .unwrap_or(table_name),
5565 context.cache,
5566 context.relationships,
5567 context.names,
5568 );
5569 let (check_sql, check_values) = {
5570 let check = crate::role::write_check(
5571 &context.caller,
5572 context.names,
5573 schema_name,
5574 table_name,
5575 crate::role::Verb::Insert,
5576 )
5577 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5578 match check {
5579 None => (String::new(), Vec::new()),
5580 Some(check) => {
5581 let mut values = Vec::new();
5582 let mut param_idx = column_names.len() + conflict_values.len() + 1;
5583 let mut alias_counter = 0usize;
5584 match build_condition(
5585 &check,
5586 &check_scope,
5587 &mut param_idx,
5588 &mut values,
5589 &mut alias_counter,
5590 )? {
5591 None => (String::new(), Vec::new()),
5592 Some(sql) => (
5593 format!(
5594 ", ({}) AS {}",
5595 sql,
5596 postrust_sql::escape_ident(CHECK_COLUMN)
5597 ),
5598 values,
5599 ),
5600 }
5601 }
5602 }
5603 };
5604
5605 let names = column_names;
5606 let written = if names.is_empty() {
5607 let sql = format!(
5610 "INSERT INTO {}.{} AS {} DEFAULT VALUES{} RETURNING {}{}",
5611 postrust_sql::escape_ident(schema_name),
5612 postrust_sql::escape_ident(table_name),
5613 qualified_table,
5614 conflict_sql,
5615 row_json(&qualified_table, &column_types),
5616 check_sql
5617 );
5618 let mut query = sqlx::query(AssertSqlSafe(sql));
5619 for value in &conflict_values {
5620 query = bind_json_value(query, value);
5621 }
5622 for value in &check_values {
5623 query = bind_json_value(query, value);
5624 }
5625 query
5626 .fetch_optional(&mut *conn)
5627 .await
5628 .map_err(|error| at_path(database_error(error), &context.objects))?
5631 } else {
5632 let placeholders: Vec<String> = names
5633 .iter()
5634 .enumerate()
5635 .map(|(i, column)| {
5636 let placeholder = format!("${}", i + 1);
5637 match columns.get(*column) {
5638 Some(value) => write_expression(&column_types, column, value, &placeholder),
5639 None => placeholder,
5640 }
5641 })
5642 .collect();
5643 let sql = format!(
5644 "INSERT INTO {}.{} AS {} ({}) VALUES ({}){} RETURNING {}{}",
5645 postrust_sql::escape_ident(schema_name),
5646 postrust_sql::escape_ident(table_name),
5647 qualified_table,
5648 names
5649 .iter()
5650 .map(|c| postrust_sql::escape_ident(c))
5651 .collect::<Vec<_>>()
5652 .join(", "),
5653 placeholders.join(", "),
5654 conflict_sql,
5655 row_json(&qualified_table, &column_types),
5656 check_sql
5657 );
5658 trace!("Executing INSERT SQL: {}", sql);
5659 let mut query = sqlx::query(AssertSqlSafe(sql));
5660 for column in &names {
5661 if let Some(value) = columns.get(*column) {
5662 query = bind_json_value(query, &write_operand(&column_types, column, value));
5663 }
5664 }
5665 for value in &conflict_values {
5666 query = bind_json_value(query, value);
5667 }
5668 for value in &check_values {
5669 query = bind_json_value(query, value);
5670 }
5671 query
5672 .fetch_optional(&mut *conn)
5673 .await
5674 .map_err(|error| at_path(database_error(error), &context.objects))?
5677 };
5678
5679 let Some(written) = written else {
5682 return Ok(serde_json::Value::Null);
5683 };
5684 refuse_unchecked_rows(std::slice::from_ref(&written), !check_sql.is_empty())
5685 .map_err(|error| at_path(error, &context.objects))?;
5686 let row: serde_json::Value = written.try_get(0).unwrap_or(serde_json::Value::Null);
5687 *written_count += 1;
5688
5689 for (relationship, data, conflict) in to_many {
5691 let plan =
5692 postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5693 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5694 let children = match data {
5695 serde_json::Value::Array(items) => items,
5696 serde_json::Value::Object(map) => vec![serde_json::Value::Object(map)],
5697 serde_json::Value::Null => Vec::new(),
5698 _ => {
5699 return Err(async_graphql::Error::new(format!(
5700 "\"{}\" takes objects to insert",
5701 relationship.name
5702 )))
5703 }
5704 };
5705 for (child_index, child) in children.into_iter().enumerate() {
5706 let serde_json::Value::Object(mut child) = child else {
5707 continue;
5708 };
5709 for (local, foreign) in &plan.columns {
5710 if child.get(foreign).is_some_and(|v| !v.is_null()) {
5714 return Err(at_path(
5715 validation_error(format!(
5716 "cannot insert \"{}\" columns as their values are already \
5717 being determined by parent insert",
5718 foreign
5719 )),
5720 &format!(
5721 "{}.{}{}",
5722 context.row,
5723 relationship.name,
5724 match relationship.is_list {
5725 true => format!(".data[{}]", child_index),
5726 false => ".data".to_string(),
5727 }
5728 ),
5729 ));
5730 }
5731 if let Some(value) = row.get(local) {
5732 child.insert(foreign.clone(), value.clone());
5733 }
5734 }
5735 let at = match relationship.is_list {
5741 true => format!(".data[{}]", child_index),
5742 false => ".data".to_string(),
5743 };
5744 let under = format!("{}.{}", context.row, relationship.name);
5745 let nested = InsertContext {
5746 on_conflict: conflict.clone(),
5747 cache: context.cache,
5748 relationships: context.relationships,
5749 type_names: context.type_names,
5750 names: context.names,
5751 caller: context.caller,
5752 row: format!("{}{}", under, at),
5753 objects: format!("{}.data", under),
5754 conflict: format!("{}.on_conflict", under),
5755 };
5756 insert_row(
5757 conn,
5758 &plan.foreign_schema,
5759 &plan.foreign_table,
5760 child,
5761 &nested,
5762 written_count,
5763 )
5764 .await?;
5765 }
5766 }
5767
5768 Ok(row)
5769 })
5770}
5771
5772#[derive(Clone)]
5778struct InsertContext<'a> {
5779 on_conflict: Option<serde_json::Value>,
5782 cache: &'a SchemaCache,
5783 relationships: &'a HashMap<String, Vec<RelationshipField>>,
5784 type_names: &'a HashMap<(String, String), String>,
5788 names: &'a crate::names::NameOverrides,
5791 caller: crate::role::Caller<'a>,
5795 row: String,
5800 objects: String,
5804 conflict: String,
5806}
5807
5808#[allow(clippy::too_many_arguments)]
5821async fn reread_returning(
5822 pool: &PgPool,
5823 gql_ctx: &GraphQLContext,
5824 schema_name: &str,
5825 table_name: &str,
5826 type_name: &str,
5827 rows: Vec<Value>,
5828 returning: async_graphql::SelectionField<'_>,
5829 relationships: &HashMap<String, Vec<RelationshipField>>,
5830 names: &crate::names::NameOverrides,
5831 max_rows: Option<i64>,
5832) -> Result<Vec<Value>, async_graphql::Error> {
5833 let guard = gql_ctx
5834 .schema_cache
5835 .get()
5836 .await
5837 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5838 let Some(cache) = guard.as_ref() else {
5839 return Ok(rows);
5840 };
5841 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5842 let Some(table) = cache.get_table(&qi) else {
5843 return Ok(rows);
5844 };
5845 if table.pk_cols.is_empty() {
5846 return Ok(rows);
5848 }
5849
5850 let mut param_idx = 1usize;
5851 let mut values: Vec<serde_json::Value> = Vec::new();
5852 let mut alias_counter = 0usize;
5853 let embeds = build_embed_expressions(
5854 &gql_ctx.caller(),
5855 cache,
5856 relationships,
5857 type_name,
5858 "src",
5859 returning.selection_set(),
5860 max_rows,
5861 &mut alias_counter,
5862 &mut param_idx,
5863 &mut values,
5864 names,
5865 )?;
5866 let computed = computed_projections(
5867 table,
5868 returning.selection_set(),
5869 "src",
5870 names,
5871 cache,
5872 &mut param_idx,
5873 &mut values,
5874 )?;
5875 let renames = names.renames_columns(&table.schema, &table.name);
5879 if embeds.is_empty() && computed.is_empty() && !renames {
5880 return Ok(rows);
5881 }
5882
5883 let mut conditions = Vec::with_capacity(rows.len());
5885 for row in &rows {
5886 let mut parts = Vec::with_capacity(table.pk_cols.len());
5887 for column in &table.pk_cols {
5888 let Value::Object(map) = row else {
5889 return Ok(rows);
5890 };
5891 let Some(value) = map.get(&async_graphql::Name::new(column)) else {
5892 return Ok(rows);
5893 };
5894 parts.push(format!(
5895 "{} = ${}",
5896 postrust_sql::escape_ident(column),
5897 param_idx
5898 ));
5899 values.push(value_to_json(value));
5900 param_idx += 1;
5901 }
5902 conditions.push(format!("({})", parts.join(" AND ")));
5903 }
5904 if conditions.is_empty() {
5905 return Ok(rows);
5906 }
5907
5908 let mut projection =
5909 rename_projection(table, "src", names).unwrap_or_else(|| "src.*".to_string());
5910 for expression in &computed {
5911 projection.push_str(", ");
5912 projection.push_str(expression);
5913 }
5914 for (name, expression) in &embeds {
5915 projection.push_str(", ");
5916 projection.push_str(expression);
5917 projection.push_str(" AS ");
5918 projection.push_str(&postrust_sql::escape_ident(name));
5919 }
5920
5921 let sql = format!(
5922 "SELECT {} FROM (SELECT {} FROM (SELECT * FROM {}.{} WHERE {}) AS src) AS pgrst_r",
5923 row_json("pgrst_r", &exposed_column_types(table, names)),
5924 projection,
5925 postrust_sql::escape_ident(schema_name),
5926 postrust_sql::escape_ident(table_name),
5927 conditions.join(" OR ")
5928 );
5929
5930 let mut guard = write_tx(gql_ctx, pool).await?;
5933 let conn = guard.as_mut().expect("write_tx opens one");
5934 let reread = execute_query_on(conn, &sql, &values).await?;
5935 drop(guard);
5936
5937 if reread.is_empty() {
5938 return Ok(rows);
5939 }
5940 Ok(reread.into_iter().map(json_to_value).collect())
5941}
5942
5943async fn execute_insert(
5945 pool: &PgPool,
5946 gql_ctx: &GraphQLContext,
5947 schema_name: &str,
5948 table_name: &str,
5949 objects: serde_json::Value,
5950 context: &InsertContext<'_>,
5951 single: bool,
5955) -> Result<(Vec<Value>, usize), async_graphql::Error> {
5956 trace!("Insert mutation for {}: {:?}", table_name, objects);
5957
5958 let objects_array = match objects {
5960 serde_json::Value::Array(arr) => arr,
5961 serde_json::Value::Object(obj) => vec![serde_json::Value::Object(obj)],
5962 _ => {
5963 return Err(async_graphql::Error::new(
5964 "objects must be an array or object",
5965 ))
5966 }
5967 };
5968
5969 if objects_array.is_empty() {
5970 return Err(async_graphql::Error::new("objects cannot be empty"));
5971 }
5972
5973 let mut guard = write_tx(gql_ctx, pool).await?;
5974 let conn = guard.as_mut().expect("write_tx opens one");
5975
5976 let mut inserted: Vec<Value> = Vec::new();
5977 let mut written = 0usize;
5978
5979 for (index, object) in objects_array.into_iter().enumerate() {
5980 let serde_json::Value::Object(map) = object else {
5981 return Err(async_graphql::Error::new(
5982 "each object to insert is an object",
5983 ));
5984 };
5985 let context = InsertContext {
5988 row: match single {
5989 true => context.objects.clone(),
5990 false => format!("{}[{}]", context.objects, index),
5991 },
5992 ..context.clone()
5993 };
5994 let row = insert_row(conn, schema_name, table_name, map, &context, &mut written).await?;
5995 if !row.is_null() {
5999 inserted.push(json_to_value(row));
6000 }
6001 }
6002
6003 Ok((inserted, written))
6004}
6005
6006fn mutation_result<'a>(rows: Vec<Value>, affected: usize, by_key: bool) -> Option<FieldValue<'a>> {
6018 if by_key {
6019 return rows.into_iter().next().map(FieldValue::value);
6020 }
6021 let mut response = async_graphql::indexmap::IndexMap::new();
6022 response.insert(
6023 async_graphql::Name::new("affected_rows"),
6024 Value::from(affected),
6025 );
6026 response.insert(async_graphql::Name::new("returning"), Value::List(rows));
6027 Some(FieldValue::value(Value::Object(response)))
6028}
6029
6030fn bind_json_value<'q>(
6042 query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
6043 value: &serde_json::Value,
6044) -> sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments> {
6045 match value {
6046 serde_json::Value::Null => query.bind(None::<String>),
6047 serde_json::Value::Bool(b) => query.bind(*b),
6048 serde_json::Value::Number(n) => {
6049 if let Some(i) = n.as_i64() {
6050 query.bind(i)
6051 } else if let Some(f) = n.as_f64() {
6052 query.bind(f)
6053 } else {
6054 query.bind(n.to_string())
6055 }
6056 }
6057 serde_json::Value::String(s) => query.bind(s.clone()),
6058 _ => query.bind(value.to_string()),
6059 }
6060}
6061
6062#[allow(clippy::too_many_arguments)] async fn execute_update(
6065 pool: &PgPool,
6066 gql_ctx: &GraphQLContext,
6067 schema_name: &str,
6068 table_name: &str,
6069 type_name: &str,
6070 operators: Vec<(&'static str, serde_json::Value)>,
6071 column_types: HashMap<String, String>,
6072 where_clause: Option<serde_json::Value>,
6073 relationships: &HashMap<String, Vec<RelationshipField>>,
6074 names: &crate::names::NameOverrides,
6075) -> Result<Vec<Value>, async_graphql::Error> {
6076 use sqlx::Row;
6077
6078 trace!("Update mutation for {}: {:?}", table_name, operators);
6079
6080 let mut set_parts: Vec<String> = Vec::new();
6085 let mut set_values: Vec<serde_json::Value> = Vec::new();
6086 let mut param_idx = 1;
6087 let mut written: HashSet<String> = HashSet::new();
6088
6089 let preset = crate::role::presets(
6099 &gql_ctx.caller(),
6100 names,
6101 schema_name,
6102 table_name,
6103 crate::role::Verb::Update,
6104 )
6105 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6106 let preset_columns: HashSet<&str> = preset.iter().map(|(name, _)| name.as_str()).collect();
6107 for (column, value) in &preset {
6108 let placeholder =
6109 write_expression(&column_types, column, value, &format!("${}", param_idx));
6110 set_parts.push(format!(
6111 "{} = {}",
6112 postrust_sql::escape_ident(column),
6113 placeholder
6114 ));
6115 set_values.push(write_operand(&column_types, column, value));
6116 param_idx += 1;
6117 }
6118
6119 for (operator, payload) in &operators {
6120 let serde_json::Value::Object(map) = payload else {
6121 return Err(async_graphql::Error::new(format!(
6122 "\"{}\" takes an object mapping columns to values",
6123 operator
6124 )));
6125 };
6126 for (field, value) in map {
6127 let column = &names
6128 .column_source(schema_name, table_name, field)
6129 .unwrap_or(field)
6130 .to_string();
6131 if preset_columns.contains(column.as_str()) {
6134 continue;
6135 }
6136 if !written.insert(column.clone()) {
6137 return Err(async_graphql::Error::new(format!(
6138 "\"{}\" is written twice in one update; a column may be \
6139 changed by one operator at a time",
6140 column
6141 )));
6142 }
6143 let quoted = postrust_sql::escape_ident(column);
6144 let placeholder = match *operator {
6153 "_set" | "_inc" => {
6154 write_expression(&column_types, column, value, &format!("${}", param_idx))
6155 }
6156 _ => format!("${}", param_idx),
6157 };
6158 let assignment = match *operator {
6161 "_set" => format!("{} = {}", quoted, placeholder),
6162 "_inc" => format!("{} = {} + {}", quoted, quoted, placeholder),
6163 "_append" => format!("{} = {} || {}::jsonb", quoted, quoted, placeholder),
6164 "_prepend" => format!("{} = {}::jsonb || {}", quoted, placeholder, quoted),
6165 "_delete_key" => format!("{} = {} - {}::text", quoted, quoted, placeholder),
6166 "_delete_elem" => format!("{} = {} - {}::int", quoted, quoted, placeholder),
6167 "_delete_at_path" => {
6173 let Some(steps) = value.as_array() else {
6174 return Err(async_graphql::Error::new(format!(
6175 "\"_delete_at_path\" on \"{}\" takes a list of keys",
6176 column
6177 )));
6178 };
6179 let path = sql_array(steps, "text[]", &mut param_idx, &mut set_values);
6180 set_parts.push(format!("{} = {} #- {}", quoted, quoted, path));
6181 continue;
6182 }
6183 other => {
6184 return Err(async_graphql::Error::new(format!(
6185 "unsupported update operator \"{}\"",
6186 other
6187 )))
6188 }
6189 };
6190 if *operator == "_set"
6191 && matches!(
6192 column_types.get(column).map(String::as_str),
6193 Some("geometry") | Some("geography")
6194 )
6195 {
6196 check_geojson(value)?;
6197 }
6198 set_parts.push(assignment);
6199 set_values.push(match *operator {
6200 "_set" => write_operand(&column_types, column, value),
6201 _ => value.clone(),
6202 });
6203 param_idx += 1;
6204 }
6205 }
6206
6207 if set_parts.is_empty() {
6212 return Ok(Vec::new());
6213 }
6214
6215 let guard = gql_ctx
6219 .schema_cache
6220 .get()
6221 .await
6222 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6223 let cache = guard
6224 .as_ref()
6225 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6226 let scope = WhereScope::table(schema_name, table_name, type_name, names)
6227 .under_alias(WRITTEN_ROW)
6228 .with_resolution(cache, relationships);
6229 let (where_sql, where_values) = build_where_clause(where_clause.as_ref(), param_idx, &scope)?;
6230
6231 if where_sql.is_empty() {
6234 return Err(async_graphql::Error::new(format!(
6235 "update on \"{}\" requires a `where` argument with at least one \
6236 recognised condition; refusing to update every row",
6237 table_name
6238 )));
6239 }
6240
6241 let (check_sql, check_values) = permission_check_sql(
6246 gql_ctx,
6247 names,
6248 schema_name,
6249 table_name,
6250 crate::role::Verb::Update,
6251 &scope,
6252 param_idx + where_values.len(),
6253 )?;
6254
6255 let sql = format!(
6256 "UPDATE {}.{} AS {} SET {} {} RETURNING {}{}",
6257 postrust_sql::escape_ident(schema_name),
6258 postrust_sql::escape_ident(table_name),
6259 postrust_sql::escape_ident(WRITTEN_ROW),
6260 set_parts.join(", "),
6261 where_sql,
6262 row_json(&postrust_sql::escape_ident(WRITTEN_ROW), &column_types),
6263 check_sql
6264 );
6265
6266 trace!("Executing UPDATE SQL: {}", sql);
6267
6268 let mut query = sqlx::query(AssertSqlSafe(sql));
6270
6271 for val in &set_values {
6273 query = bind_json_value(query, val);
6274 }
6275
6276 for val in &where_values {
6278 query = bind_json_value(query, val);
6279 }
6280
6281 for val in &check_values {
6283 query = bind_json_value(query, val);
6284 }
6285
6286 let mut guard = write_tx(gql_ctx, pool).await?;
6287 let conn = guard.as_mut().expect("write_tx opens one");
6288 let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6289
6290 refuse_unchecked_rows(&rows, !check_sql.is_empty())?;
6291
6292 let updated: Vec<Value> = rows
6293 .iter()
6294 .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6295 .map(json_to_value)
6296 .collect();
6297
6298 Ok(updated)
6299}
6300
6301fn permission_check_sql(
6308 gql_ctx: &GraphQLContext,
6309 names: &crate::names::NameOverrides,
6310 schema_name: &str,
6311 table_name: &str,
6312 verb: crate::role::Verb,
6313 scope: &WhereScope<'_>,
6314 start_param_idx: usize,
6315) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6316 let check = crate::role::write_check(&gql_ctx.caller(), names, schema_name, table_name, verb)
6317 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6318 let Some(check) = check else {
6319 return Ok((String::new(), Vec::new()));
6320 };
6321
6322 let mut values = Vec::new();
6323 let mut param_idx = start_param_idx;
6324 let mut alias_counter = 0usize;
6325 let condition = build_condition(
6326 &check,
6327 scope,
6328 &mut param_idx,
6329 &mut values,
6330 &mut alias_counter,
6331 )?;
6332 Ok(match condition {
6333 None => (String::new(), Vec::new()),
6336 Some(sql) => (
6337 format!(
6338 ", ({}) AS {}",
6339 sql,
6340 postrust_sql::escape_ident(CHECK_COLUMN)
6341 ),
6342 values,
6343 ),
6344 })
6345}
6346
6347const CHECK_COLUMN: &str = "pgrst_permission_check";
6349
6350fn refuse_unchecked_rows(
6357 rows: &[sqlx::postgres::PgRow],
6358 checked: bool,
6359) -> Result<(), async_graphql::Error> {
6360 if !checked {
6361 return Ok(());
6362 }
6363 use sqlx::Row;
6364 let all_passed = rows
6365 .iter()
6366 .all(|row| row.try_get::<bool, _>(CHECK_COLUMN).unwrap_or(false));
6367 match all_passed {
6368 true => Ok(()),
6369 false => Err(coded_error("permission-error", crate::role::CHECK_FAILED)),
6370 }
6371}
6372
6373#[allow(clippy::too_many_arguments)] async fn execute_delete(
6376 pool: &PgPool,
6377 gql_ctx: &GraphQLContext,
6378 schema_name: &str,
6379 table_name: &str,
6380 type_name: &str,
6381 where_clause: Option<serde_json::Value>,
6382 returning: Option<async_graphql::SelectionField<'_>>,
6383 relationships: &HashMap<String, Vec<RelationshipField>>,
6384 names: &crate::names::NameOverrides,
6385 max_rows: Option<i64>,
6386) -> Result<Vec<Value>, async_graphql::Error> {
6387 use sqlx::Row;
6388
6389 trace!("Delete mutation for {}", table_name);
6390
6391 let guard = gql_ctx
6392 .schema_cache
6393 .get()
6394 .await
6395 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6396 let cache = guard
6397 .as_ref()
6398 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6399 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
6400 let column_types = cache
6401 .get_table(&qi)
6402 .map(|table| exposed_column_types(table, names))
6403 .unwrap_or_default();
6404
6405 let scope = WhereScope::table(schema_name, table_name, type_name, names)
6409 .under_alias(WRITTEN_ROW)
6410 .with_resolution(cache, relationships);
6411 let (where_sql, mut values) = build_where_clause(where_clause.as_ref(), 1, &scope)?;
6412
6413 if where_sql.is_empty() {
6416 return Err(async_graphql::Error::new(format!(
6417 "delete on \"{}\" requires a `where` argument with at least one \
6418 recognised condition; refusing to delete every row",
6419 table_name
6420 )));
6421 }
6422
6423 let mut projection = String::new();
6429 let renamed = cache
6430 .get_table(&qi)
6431 .and_then(|table| rename_projection(table, "src", names));
6432 if let Some(returning) = returning {
6433 let mut param_idx = values.len() + 1;
6434 let mut alias_counter = 0usize;
6435 let embeds = build_embed_expressions(
6436 &gql_ctx.caller(),
6437 cache,
6438 relationships,
6439 type_name,
6440 "src",
6441 returning.selection_set(),
6442 max_rows,
6443 &mut alias_counter,
6444 &mut param_idx,
6445 &mut values,
6446 names,
6447 )?;
6448 let computed = match cache.get_table(&qi) {
6449 Some(table) => computed_projections(
6450 table,
6451 returning.selection_set(),
6452 "src",
6453 names,
6454 cache,
6455 &mut param_idx,
6456 &mut values,
6457 )?,
6458 None => Vec::new(),
6459 };
6460 for expression in &computed {
6461 projection.push_str(", ");
6462 projection.push_str(expression);
6463 }
6464 for (name, expression) in &embeds {
6465 projection.push_str(", ");
6466 projection.push_str(expression);
6467 projection.push_str(" AS ");
6468 projection.push_str(&postrust_sql::escape_ident(name));
6469 }
6470 }
6471
6472 let written = postrust_sql::escape_ident(WRITTEN_ROW);
6473 let sql = if projection.is_empty() && renamed.is_none() {
6474 format!(
6475 "DELETE FROM {}.{} AS {} {} RETURNING {}",
6476 postrust_sql::escape_ident(schema_name),
6477 postrust_sql::escape_ident(table_name),
6478 written,
6479 where_sql,
6480 row_json(&written, &column_types)
6481 )
6482 } else {
6483 format!(
6484 "WITH pgrst_deleted AS (DELETE FROM {}.{} AS {} {} RETURNING {}.*) \
6485 SELECT {} FROM (SELECT {}{} FROM pgrst_deleted AS src) AS pgrst_r",
6486 postrust_sql::escape_ident(schema_name),
6487 postrust_sql::escape_ident(table_name),
6488 written,
6489 where_sql,
6490 written,
6491 row_json("pgrst_r", &column_types),
6492 renamed.unwrap_or_else(|| "src.*".to_string()),
6493 projection
6494 )
6495 };
6496
6497 trace!("Executing DELETE SQL: {}", sql);
6498
6499 let mut query = sqlx::query(AssertSqlSafe(sql));
6500 for val in &values {
6501 query = bind_json_value(query, val);
6502 }
6503
6504 let mut guard = write_tx(gql_ctx, pool).await?;
6505 let conn = guard.as_mut().expect("write_tx opens one");
6506 let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6507
6508 let deleted: Vec<Value> = rows
6509 .iter()
6510 .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6511 .map(json_to_value)
6512 .collect();
6513
6514 Ok(deleted)
6515}
6516
6517fn permission_predicate(
6525 caller: &crate::role::Caller<'_>,
6526 names: &crate::names::NameOverrides,
6527 schema: &str,
6528 table: &str,
6529) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6530 permission_filter(caller, names, schema, table, crate::role::Verb::Select)
6531}
6532
6533fn permission_filter(
6535 caller: &crate::role::Caller<'_>,
6536 names: &crate::names::NameOverrides,
6537 schema: &str,
6538 table: &str,
6539 verb: crate::role::Verb,
6540) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6541 crate::role::row_filter(caller, names, schema, table, verb)
6542 .map(|filter| filter.map(|predicate| serde_json::json!({ PERMISSION: predicate })))
6543 .map_err(|fault| coded_error(fault.code(), fault.to_string()))
6544}
6545
6546const PERMISSION: &str = "_permission";
6567
6568fn permission_limit(
6574 caller: &crate::role::Caller<'_>,
6575 names: &crate::names::NameOverrides,
6576 schema: &str,
6577 table: &str,
6578) -> Option<i64> {
6579 crate::role::read_limit(caller, names, schema, table)
6580}
6581
6582fn and_predicates(
6589 request: Option<serde_json::Value>,
6590 permission: Option<serde_json::Value>,
6591) -> Option<serde_json::Value> {
6592 match (request, permission) {
6593 (None, None) => None,
6594 (Some(only), None) | (None, Some(only)) => Some(only),
6595 (Some(request), Some(permission)) => {
6596 Some(serde_json::json!({ "_and": [request, permission] }))
6597 }
6598 }
6599}
6600
6601fn build_where_clause(
6611 where_value: Option<&serde_json::Value>,
6612 start_param_idx: usize,
6613 scope: &WhereScope<'_>,
6614) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6615 let mut values: Vec<serde_json::Value> = Vec::new();
6616 let mut param_idx = start_param_idx;
6617 let mut alias_counter = 0usize;
6618
6619 let condition = match where_value {
6620 Some(value) => build_condition(
6621 value,
6622 scope,
6623 &mut param_idx,
6624 &mut values,
6625 &mut alias_counter,
6626 )?,
6627 None => None,
6628 };
6629
6630 Ok(match condition {
6631 Some(sql) => (format!("WHERE {}", sql), values),
6632 None => (String::new(), values),
6633 })
6634}
6635
6636pub struct WhereScope<'a> {
6648 qualified: postrust_core::api_request::QualifiedIdentifier,
6650 sql_ref: String,
6652 row_ref: String,
6656 type_name: String,
6658 root_ref: String,
6666 root_qualified: postrust_core::api_request::QualifiedIdentifier,
6667 names: &'a crate::names::NameOverrides,
6670 resolution: Option<WhereResolution<'a>>,
6671 caller: Option<crate::role::Caller<'a>>,
6678}
6679
6680struct WhereResolution<'a> {
6682 cache: &'a SchemaCache,
6683 relationships: &'a HashMap<String, Vec<RelationshipField>>,
6684}
6685
6686impl<'a> WhereScope<'a> {
6687 pub fn table(
6689 schema: &str,
6690 table: &str,
6691 type_name: &str,
6692 names: &'a crate::names::NameOverrides,
6693 ) -> Self {
6694 Self {
6695 qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6696 sql_ref: format!(
6697 "{}.{}",
6698 postrust_sql::escape_ident(schema),
6699 postrust_sql::escape_ident(table)
6700 ),
6701 row_ref: postrust_sql::escape_ident(table),
6702 type_name: type_name.to_string(),
6703 root_ref: format!(
6704 "{}.{}",
6705 postrust_sql::escape_ident(schema),
6706 postrust_sql::escape_ident(table)
6707 ),
6708 root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6709 names,
6710 resolution: None,
6711 caller: None,
6712 }
6713 }
6714
6715 fn under_alias(mut self, alias: &str) -> Self {
6723 if self.root_ref == self.sql_ref {
6726 self.root_ref = postrust_sql::escape_ident(alias);
6727 }
6728 self.sql_ref = postrust_sql::escape_ident(alias);
6729 self.row_ref = postrust_sql::escape_ident(alias);
6730 self
6731 }
6732
6733 fn with_resolution(
6735 mut self,
6736 cache: &'a SchemaCache,
6737 relationships: &'a HashMap<String, Vec<RelationshipField>>,
6738 ) -> Self {
6739 self.resolution = Some(WhereResolution {
6740 cache,
6741 relationships,
6742 });
6743 self
6744 }
6745
6746 #[allow(clippy::too_many_arguments)]
6748 fn for_alias(
6749 schema: &str,
6750 table: &str,
6751 alias: &str,
6752 type_name: &str,
6753 cache: &'a SchemaCache,
6754 relationships: &'a HashMap<String, Vec<RelationshipField>>,
6755 names: &'a crate::names::NameOverrides,
6756 ) -> Self {
6757 Self {
6758 qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6759 sql_ref: postrust_sql::escape_ident(alias),
6760 row_ref: postrust_sql::escape_ident(alias),
6761 type_name: type_name.to_string(),
6762 root_ref: postrust_sql::escape_ident(alias),
6763 root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6764 names,
6765 resolution: Some(WhereResolution {
6766 cache,
6767 relationships,
6768 }),
6769 caller: None,
6770 }
6771 }
6772
6773 fn aliased(
6780 schema: &str,
6781 table: &str,
6782 alias: &str,
6783 type_name: &str,
6784 from: &WhereScope<'a>,
6785 ) -> Self {
6786 Self {
6787 qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6788 sql_ref: postrust_sql::escape_ident(alias),
6789 row_ref: postrust_sql::escape_ident(alias),
6790 type_name: type_name.to_string(),
6791 root_ref: from.root_ref.clone(),
6794 root_qualified: from.root_qualified.clone(),
6795 names: from.names,
6796 resolution: from.resolution.as_ref().map(|r| WhereResolution {
6797 cache: r.cache,
6798 relationships: r.relationships,
6799 }),
6800 caller: from.caller,
6804 }
6805 }
6806
6807 pub fn for_caller(mut self, caller: crate::role::Caller<'a>) -> Self {
6809 self.caller = Some(caller);
6810 self
6811 }
6812
6813 fn as_system(&self) -> WhereScope<'a> {
6816 WhereScope {
6817 qualified: self.qualified.clone(),
6818 sql_ref: self.sql_ref.clone(),
6819 row_ref: self.row_ref.clone(),
6820 type_name: self.type_name.clone(),
6821 root_ref: self.sql_ref.clone(),
6828 root_qualified: self.qualified.clone(),
6829 names: self.names,
6830 resolution: self.resolution.as_ref().map(|r| WhereResolution {
6831 cache: r.cache,
6832 relationships: r.relationships,
6833 }),
6834 caller: None,
6835 }
6836 }
6837
6838 fn column(&self, name: &str) -> String {
6846 let column = self.column_name(name);
6847 let plain = format!("{}.{}", self.sql_ref, postrust_sql::escape_ident(column));
6848 let Some(cache) = self.resolution.as_ref().map(|r| r.cache) else {
6849 return plain;
6850 };
6851 let Some(table) = cache.get_table(&self.qualified) else {
6852 return plain;
6853 };
6854 if table.get_column(column).is_some() {
6855 return plain;
6856 }
6857 let function = self
6858 .names
6859 .computed_source(&table.schema, &table.name, name)
6860 .unwrap_or(name);
6861 match table.get_computed_column(function) {
6862 Some(definition) => {
6863 computed_call(definition, &format!("{}.*", self.row_ref), &self.row_ref)
6864 }
6865 None => plain,
6866 }
6867 }
6868
6869 fn column_name<'n>(&'n self, name: &'n str) -> &'n str {
6871 self.names
6872 .column_source(&self.qualified.schema, &self.qualified.name, name)
6873 .unwrap_or(name)
6874 }
6875
6876 fn column_type(&self, name: &str) -> Option<String> {
6880 let cache = self.resolution.as_ref()?.cache;
6881 let table = cache.get_table(&self.qualified)?;
6882 table
6883 .get_column(self.column_name(name))
6884 .map(|c| c.nominal_type.clone())
6885 }
6886
6887 fn other_column(
6901 &self,
6902 operand: &serde_json::Value,
6903 op: &str,
6904 ) -> Result<String, async_graphql::Error> {
6905 use serde_json::Value;
6906
6907 let refused = |what: &str| {
6908 async_graphql::Error::new(format!(
6909 "the \"{}\" comparison takes the name of a column, {}",
6910 op, what
6911 ))
6912 };
6913 let (against, qualified, name) = match operand {
6914 Value::String(name) => (&self.sql_ref, &self.qualified, name.as_str()),
6915 Value::Array(path) => match path.as_slice() {
6916 [Value::String(name)] => (&self.sql_ref, &self.qualified, name.as_str()),
6917 [Value::String(root), Value::String(name)] if root == "$" => {
6918 (&self.root_ref, &self.root_qualified, name.as_str())
6919 }
6920 _ => return Err(refused("or [\"$\", \"a column\"]")),
6921 },
6922 _ => return Err(refused("written as a string")),
6923 };
6924 let column = self
6927 .names
6928 .column_source(&qualified.schema, &qualified.name, name)
6929 .unwrap_or(name);
6930 Ok(format!(
6931 "{}.{}",
6932 against,
6933 postrust_sql::escape_ident(column)
6934 ))
6935 }
6936
6937 fn relationship(&self, name: &str) -> Option<std::borrow::Cow<'a, RelationshipField>> {
6951 let resolution = self.resolution.as_ref()?;
6952 let published = resolution
6953 .relationships
6954 .get(&self.type_name)
6955 .and_then(|fields| fields.iter().find(|r| r.name == name));
6956 if let Some(found) = published {
6957 return Some(std::borrow::Cow::Borrowed(found));
6958 }
6959
6960 let table = resolution.cache.get_table(&self.qualified)?;
6961
6962 if let Some(declared) = self
6968 .names
6969 .declared_relationships(&table.schema, &table.name)
6970 {
6971 if let Some(entry) = declared.iter().find(|entry| entry.name == name) {
6972 let target = entry
6975 .target(&table.schema)
6976 .map(|(_, target)| target)
6977 .unwrap_or_default();
6978 if let Some(field) = crate::schema::mapped_relationship_field(
6979 entry,
6980 table,
6981 resolution.cache,
6982 &table.schema,
6983 &target,
6984 &target,
6985 &target,
6986 ) {
6987 return Some(std::borrow::Cow::Owned(field));
6988 }
6989 if let Some(key) = entry.using.as_deref() {
6993 if let Some(found) = resolution
6994 .cache
6995 .get_relationships(&self.qualified, &table.schema)
6996 .into_iter()
6997 .flatten()
6998 .find(|rel| {
6999 crate::schema::relationship_keys(rel)
7000 .iter()
7001 .any(|found| found == key)
7002 })
7003 {
7004 return Some(std::borrow::Cow::Owned(
7005 crate::schema::relationship::RelationshipField::from_relationship_named(
7006 found,
7007 &found.foreign_table().name,
7008 Some(&entry.name),
7009 ),
7010 ));
7011 }
7012 }
7013 }
7014 }
7015
7016 resolution
7017 .cache
7018 .get_relationships(&self.qualified, &table.schema)?
7019 .iter()
7020 .find_map(|rel| {
7021 let given = crate::schema::relationship_keys(rel)
7022 .iter()
7023 .find_map(|key| self.names.relationship(&table.schema, &table.name, key));
7024 let field = RelationshipField::from_relationship_named(
7028 rel,
7029 &rel.foreign_table().name,
7030 given,
7031 );
7032 (field.name == name).then_some(std::borrow::Cow::Owned(field))
7033 })
7034 }
7035}
7036
7037fn column_comparison(op: &str) -> Option<&'static str> {
7046 Some(match op {
7047 "_ceq" => "=",
7048 "_cne" => "<>",
7049 "_cgt" => ">",
7050 "_cgte" => ">=",
7051 "_clt" => "<",
7052 "_clte" => "<=",
7053 _ => return None,
7054 })
7055}
7056
7057fn build_condition(
7059 value: &serde_json::Value,
7060 scope: &WhereScope<'_>,
7061 param_idx: &mut usize,
7062 values: &mut Vec<serde_json::Value>,
7063 alias_counter: &mut usize,
7064) -> Result<Option<String>, async_graphql::Error> {
7065 let serde_json::Value::Object(map) = value else {
7066 return Ok(None);
7067 };
7068
7069 let mut conditions: Vec<String> = Vec::new();
7070
7071 for (key, val) in map {
7072 match key.as_str() {
7073 "_and" | "_or" => {
7077 let members = val.as_array().ok_or_else(|| {
7078 async_graphql::Error::new(format!("\"{}\" takes a list of expressions", key))
7079 })?;
7080 let mut parts = Vec::new();
7081 for member in members {
7082 if let Some(sql) =
7083 build_condition(member, scope, param_idx, values, alias_counter)?
7084 {
7085 parts.push(sql);
7086 }
7087 }
7088 if parts.is_empty() {
7089 if key == "_or" {
7090 conditions.push("false".to_string());
7091 }
7092 continue;
7093 }
7094 let joiner = if key == "_and" { " AND " } else { " OR " };
7095 conditions.push(format!("({})", parts.join(joiner)));
7096 }
7097 "_not" => {
7098 if let Some(sql) = build_condition(val, scope, param_idx, values, alias_counter)? {
7099 conditions.push(format!("NOT ({})", sql));
7100 }
7101 }
7102 PERMISSION => {
7105 if let Some(sql) =
7106 build_condition(val, &scope.as_system(), param_idx, values, alias_counter)?
7107 {
7108 conditions.push(sql);
7109 }
7110 }
7111 "_exists" => {
7115 conditions.push(table_exists_sql(
7116 val,
7117 scope,
7118 param_idx,
7119 values,
7120 alias_counter,
7121 )?);
7122 }
7123 name if scope.relationship(name).is_some() => {
7129 let relationship = scope.relationship(name).expect("just checked");
7130 conditions.push(exists_sql(
7131 &relationship,
7132 val,
7133 scope,
7134 param_idx,
7135 values,
7136 alias_counter,
7137 )?);
7138 }
7139 name if scope.column_type(name).is_none()
7144 && name
7145 .strip_suffix("_aggregate")
7146 .and_then(|rel| scope.relationship(rel))
7147 .is_some_and(|rel| rel.is_list) =>
7148 {
7149 let relationship = name
7150 .strip_suffix("_aggregate")
7151 .and_then(|rel| scope.relationship(rel))
7152 .expect("just checked");
7153 conditions.push(aggregate_predicate_sql(
7154 &relationship,
7155 val,
7156 scope,
7157 param_idx,
7158 values,
7159 alias_counter,
7160 )?);
7161 }
7162 column => {
7163 let quoted = scope.column(column);
7164 match val {
7165 serde_json::Value::Object(ops) => {
7166 let column_type = scope.column_type(column);
7167 for (op, operand) in ops {
7168 if let Some(other) = column_comparison(op) {
7174 conditions.push(format!(
7175 "{} {} {}",
7176 quoted,
7177 other,
7178 scope.other_column(operand, op)?
7179 ));
7180 continue;
7181 }
7182 conditions.push(comparison_sql(
7183 "ed,
7184 column,
7185 column_type.as_deref(),
7186 op,
7187 operand,
7188 param_idx,
7189 values,
7190 )?);
7191 }
7192 }
7193 other => {
7197 conditions.push(format!("{} = ${}", quoted, param_idx));
7198 values.push(other.clone());
7199 *param_idx += 1;
7200 }
7201 }
7202 }
7203 }
7204 }
7205
7206 Ok(if conditions.is_empty() {
7207 None
7208 } else if conditions.len() == 1 {
7209 conditions.pop()
7210 } else {
7211 Some(conditions.join(" AND "))
7212 })
7213}
7214
7215fn exists_sql(
7222 relationship: &RelationshipField,
7223 child_expression: &serde_json::Value,
7224 scope: &WhereScope<'_>,
7225 param_idx: &mut usize,
7226 values: &mut Vec<serde_json::Value>,
7227 alias_counter: &mut usize,
7228) -> Result<String, async_graphql::Error> {
7229 let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7230 async_graphql::Error::new(format!(
7231 "filtering on the relationship \"{}\" is not available here",
7232 relationship.name
7233 ))
7234 })?;
7235
7236 let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7237 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7238
7239 if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7240 return Err(async_graphql::Error::new(format!(
7241 "filtering on \"{}\" is not supported: it is reached through a junction \
7242 rather than by a key",
7243 relationship.name
7244 )));
7245 }
7246
7247 *alias_counter += 1;
7248 let alias = format!("pgrst_rel_{}", alias_counter);
7249 let child_scope = WhereScope::aliased(
7250 &plan.foreign_schema,
7251 &plan.foreign_table,
7252 &alias,
7253 &relationship.target_type,
7254 scope,
7255 );
7256
7257 let (source, mut correlation) = match &plan.function {
7260 Some(function) => (
7261 format!(
7262 "{}.{}({})",
7263 postrust_sql::escape_ident(&function.schema),
7264 postrust_sql::escape_ident(&function.name),
7265 scope.row_ref
7266 ),
7267 Vec::new(),
7268 ),
7269 None => {
7270 let mut columns = Vec::with_capacity(plan.columns.len());
7271 for (parent_column, child_column) in &plan.columns {
7272 columns.push(format!(
7273 "{} = {}",
7274 scope.column(parent_column),
7275 child_scope.column(child_column)
7276 ));
7277 }
7278 (
7279 format!(
7280 "{}.{}",
7281 postrust_sql::escape_ident(&plan.foreign_schema),
7282 postrust_sql::escape_ident(&plan.foreign_table)
7283 ),
7284 columns,
7285 )
7286 }
7287 };
7288
7289 let child_condition = build_condition(
7290 child_expression,
7291 &child_scope,
7292 param_idx,
7293 values,
7294 alias_counter,
7295 )?;
7296 if let Some(sql) = child_condition {
7297 correlation.push(format!("({})", sql));
7298 }
7299
7300 if let Some(caller) = &child_scope.caller {
7305 let permission = permission_predicate(
7312 caller,
7313 child_scope.names,
7314 &plan.foreign_schema,
7315 &plan.foreign_table,
7316 )?;
7317 if let Some(predicate) = permission {
7318 if let Some(sql) =
7319 build_condition(&predicate, &child_scope, param_idx, values, alias_counter)?
7320 {
7321 correlation.push(format!("({})", sql));
7322 }
7323 }
7324 }
7325 if correlation.is_empty() {
7326 correlation.push("true".to_string());
7327 }
7328
7329 Ok(format!(
7330 "EXISTS (SELECT 1 FROM {} AS {} WHERE {})",
7331 source,
7332 postrust_sql::escape_ident(&alias),
7333 correlation.join(" AND ")
7334 ))
7335}
7336
7337fn table_exists_sql(
7362 spec: &serde_json::Value,
7363 scope: &WhereScope<'_>,
7364 param_idx: &mut usize,
7365 values: &mut Vec<serde_json::Value>,
7366 alias_counter: &mut usize,
7367) -> Result<String, async_graphql::Error> {
7368 let Some((schema, table)) = crate::role::exists_target(spec, &scope.qualified.schema) else {
7369 return Err(async_graphql::Error::new(
7370 "\"_exists\" needs a \"_table\" to look in",
7371 ));
7372 };
7373
7374 let qualified = postrust_core::api_request::QualifiedIdentifier::new(&schema, &table);
7378 if scope
7379 .resolution
7380 .as_ref()
7381 .is_some_and(|r| r.cache.get_table(&qualified).is_none())
7382 {
7383 return Err(async_graphql::Error::new(format!(
7384 "\"_exists\" names a table this server does not have: \"{}.{}\"",
7385 schema, table
7386 )));
7387 }
7388
7389 *alias_counter += 1;
7390 let alias = format!("pgrst_exists_{}", alias_counter);
7391 let type_name = scope
7395 .names
7396 .base_name(&schema, &table)
7397 .unwrap_or(&table)
7398 .to_string();
7399 let mut inner = WhereScope::aliased(&schema, &table, &alias, &type_name, scope);
7400 inner.caller = None;
7403
7404 let condition = match spec.get("_where") {
7405 Some(predicate) => build_condition(predicate, &inner, param_idx, values, alias_counter)?,
7406 None => None,
7407 };
7408
7409 Ok(format!(
7410 "EXISTS (SELECT 1 FROM {}.{} AS {} WHERE {})",
7411 postrust_sql::escape_ident(&schema),
7412 postrust_sql::escape_ident(&table),
7413 postrust_sql::escape_ident(&alias),
7414 condition.unwrap_or_else(|| "true".to_string())
7415 ))
7416}
7417
7418fn aggregate_predicate_sql(
7433 relationship: &RelationshipField,
7434 expression: &serde_json::Value,
7435 scope: &WhereScope<'_>,
7436 param_idx: &mut usize,
7437 values: &mut Vec<serde_json::Value>,
7438 alias_counter: &mut usize,
7439) -> Result<String, async_graphql::Error> {
7440 let serde_json::Value::Object(over) = expression else {
7441 return Ok("true".to_string());
7442 };
7443
7444 let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7445 async_graphql::Error::new(format!(
7446 "filtering on the relationship \"{}\" is not available here",
7447 relationship.name
7448 ))
7449 })?;
7450 let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7451 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7452 if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7453 return Err(async_graphql::Error::new(format!(
7454 "aggregating over \"{}\" is not supported: it is reached through a junction \
7455 rather than by a key",
7456 relationship.name
7457 )));
7458 }
7459
7460 let mut conditions: Vec<String> = Vec::new();
7461 for (function, spec) in over {
7462 if spec.is_null() {
7463 continue;
7464 }
7465 let serde_json::Value::Object(spec) = spec else {
7466 continue;
7467 };
7468 let Some(predicate) = spec.get("predicate").filter(|p| !p.is_null()) else {
7469 return Err(validation_error(format!(
7470 "\"{}\" over \"{}\" needs a predicate",
7471 function, relationship.name
7472 )));
7473 };
7474
7475 *alias_counter += 1;
7476 let alias = format!("pgrst_agg_{}", alias_counter);
7477 let child_scope = WhereScope::aliased(
7478 &plan.foreign_schema,
7479 &plan.foreign_table,
7480 &alias,
7481 &relationship.target_type,
7482 scope,
7483 );
7484
7485 let (source, mut correlation) = match &plan.function {
7488 Some(routine) => (
7489 format!(
7490 "{}.{}({})",
7491 postrust_sql::escape_ident(&routine.schema),
7492 postrust_sql::escape_ident(&routine.name),
7493 scope.row_ref
7494 ),
7495 Vec::new(),
7496 ),
7497 None => {
7498 let mut columns = Vec::with_capacity(plan.columns.len());
7499 for (parent_column, child_column) in &plan.columns {
7500 columns.push(format!(
7501 "{} = {}",
7502 scope.column(parent_column),
7503 child_scope.column(child_column)
7504 ));
7505 }
7506 (
7507 format!(
7508 "{}.{}",
7509 postrust_sql::escape_ident(&plan.foreign_schema),
7510 postrust_sql::escape_ident(&plan.foreign_table)
7511 ),
7512 columns,
7513 )
7514 }
7515 };
7516 if let Some(filter) = spec.get("filter").filter(|f| !f.is_null()) {
7519 if let Some(sql) =
7520 build_condition(filter, &child_scope, param_idx, values, alias_counter)?
7521 {
7522 correlation.push(format!("({})", sql));
7523 }
7524 }
7525 if correlation.is_empty() {
7526 correlation.push("true".to_string());
7527 }
7528
7529 let arguments = spec.get("arguments").filter(|a| !a.is_null());
7530 let distinct = matches!(spec.get("distinct"), Some(serde_json::Value::Bool(true)));
7531 let (aggregate, operand_type) = match function.as_str() {
7532 "count" => {
7533 let columns: Vec<String> = match arguments {
7534 Some(serde_json::Value::Array(items)) => items
7535 .iter()
7536 .filter_map(|item| item.as_str())
7537 .map(|column| child_scope.column(column))
7538 .collect(),
7539 Some(serde_json::Value::String(one)) => vec![child_scope.column(one)],
7540 _ => Vec::new(),
7541 };
7542 let counted = match columns.len() {
7543 0 => "*".to_string(),
7544 1 => columns.into_iter().next().unwrap_or_default(),
7545 _ => format!("({})", columns.join(", ")),
7546 };
7547 let counted = match distinct && counted != "*" {
7548 true => format!("DISTINCT {}", counted),
7549 false => counted,
7550 };
7551 (format!("count({})", counted), "integer")
7552 }
7553 "bool_and" | "bool_or" => {
7554 let Some(column) = arguments.and_then(|a| a.as_str()) else {
7555 return Err(validation_error(format!(
7556 "\"{}\" over \"{}\" needs the column to fold",
7557 function, relationship.name
7558 )));
7559 };
7560 (
7561 format!("{}({})", function, child_scope.column(column)),
7562 "boolean",
7563 )
7564 }
7565 other => {
7566 return Err(validation_error(format!(
7567 "\"{}\" is not an aggregate a predicate can be written over",
7568 other
7569 )))
7570 }
7571 };
7572
7573 let subselect = format!(
7574 "(SELECT {} FROM {} AS {} WHERE {})",
7575 aggregate,
7576 source,
7577 postrust_sql::escape_ident(&alias),
7578 correlation.join(" AND ")
7579 );
7580 let serde_json::Value::Object(operators) = predicate else {
7581 return Err(validation_error(format!(
7582 "the predicate on \"{}\" is not a comparison",
7583 function
7584 )));
7585 };
7586 for (operator, operand) in operators {
7587 conditions.push(comparison_sql(
7588 &subselect,
7589 function,
7590 Some(operand_type),
7591 operator,
7592 operand,
7593 param_idx,
7594 values,
7595 )?);
7596 }
7597 }
7598
7599 Ok(match conditions.is_empty() {
7600 true => "true".to_string(),
7601 false => format!("({})", conditions.join(" AND ")),
7602 })
7603}
7604
7605#[allow(clippy::too_many_arguments)]
7613fn postgis_sql(
7614 quoted: &str,
7615 column_type: Option<&str>,
7616 function: &str,
7617 op: &str,
7618 operand: &serde_json::Value,
7619 param_idx: &mut usize,
7620 values: &mut Vec<serde_json::Value>,
7621) -> Result<String, async_graphql::Error> {
7622 let is_geography = column_type == Some("geography");
7623 let mut shape = |value: &serde_json::Value, param_idx: &mut usize| -> String {
7624 let placeholder = format!("${}", param_idx);
7625 *param_idx += 1;
7626 values.push(value.clone());
7627 match is_geography {
7628 true => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
7629 false => format!("ST_GeomFromGeoJSON({})", placeholder),
7630 }
7631 };
7632
7633 match op {
7634 "_st_d_within" | "_st_3d_d_within" => {
7636 let serde_json::Value::Object(spec) = operand else {
7637 return Err(async_graphql::Error::new(format!(
7638 "\"{}\" takes {{distance, from}}",
7639 op
7640 )));
7641 };
7642 let Some(from) = spec.get("from") else {
7643 return Err(async_graphql::Error::new(format!(
7644 "\"{}\" needs a shape to measure from",
7645 op
7646 )));
7647 };
7648 let Some(distance) = spec.get("distance") else {
7649 return Err(async_graphql::Error::new(format!(
7650 "\"{}\" needs a distance",
7651 op
7652 )));
7653 };
7654 let from_sql = shape(from, param_idx);
7655 let distance_sql = format!("${}", param_idx);
7656 *param_idx += 1;
7657 values.push(distance.clone());
7658 let spheroid = match (is_geography, spec.get("use_spheroid")) {
7663 (true, Some(use_spheroid)) if !use_spheroid.is_null() => {
7664 let placeholder = format!("${}", param_idx);
7665 *param_idx += 1;
7666 values.push(use_spheroid.clone());
7667 format!(", {}::boolean", placeholder)
7668 }
7669 _ => String::new(),
7670 };
7671 Ok(format!(
7672 "{}({}, {}, {}::float8{})",
7673 function, quoted, from_sql, distance_sql, spheroid
7674 ))
7675 }
7676 "_st_intersects_rast" => {
7678 let placeholder = format!("${}", param_idx);
7679 *param_idx += 1;
7680 values.push(operand.clone());
7681 Ok(format!("{}({}, {}::raster)", function, quoted, placeholder))
7682 }
7683 "_st_intersects_geom_nband" | "_st_intersects_nband_geom" => {
7685 let serde_json::Value::Object(spec) = operand else {
7686 return Err(async_graphql::Error::new(format!(
7687 "\"{}\" takes an object",
7688 op
7689 )));
7690 };
7691 let Some(geometry) = spec.get("geommin") else {
7692 return Err(async_graphql::Error::new(format!(
7693 "\"{}\" needs a shape",
7694 op
7695 )));
7696 };
7697 let band = spec.get("nband").filter(|v| !v.is_null());
7698 let geometry_sql = shape(geometry, param_idx);
7699 Ok(match band {
7700 None => format!("{}({}, {})", function, quoted, geometry_sql),
7701 Some(band) => {
7702 let placeholder = format!("${}", param_idx);
7703 *param_idx += 1;
7704 values.push(band.clone());
7705 format!(
7709 "{}({}, {}::int, {})",
7710 function, quoted, placeholder, geometry_sql
7711 )
7712 }
7713 })
7714 }
7715 _ => {
7717 let other = shape(operand, param_idx);
7718 Ok(format!("{}({}, {})", function, quoted, other))
7719 }
7720 }
7721}
7722
7723fn sql_array(
7729 items: &[serde_json::Value],
7730 cast: &str,
7731 param_idx: &mut usize,
7732 values: &mut Vec<serde_json::Value>,
7733) -> String {
7734 if items.is_empty() {
7735 return format!("ARRAY[]::{}", cast);
7736 }
7737 let placeholders: Vec<String> = items
7738 .iter()
7739 .map(|item| {
7740 let placeholder = format!("${}", param_idx);
7741 *param_idx += 1;
7742 values.push(item.clone());
7743 placeholder
7744 })
7745 .collect();
7746 format!("ARRAY[{}]::{}", placeholders.join(", "), cast)
7747}
7748
7749#[allow(clippy::too_many_arguments)]
7751fn comparison_sql(
7752 quoted: &str,
7753 column: &str,
7754 column_type: Option<&str>,
7755 op: &str,
7756 operand: &serde_json::Value,
7757 param_idx: &mut usize,
7758 values: &mut Vec<serde_json::Value>,
7759) -> Result<String, async_graphql::Error> {
7760 if op == "_cast" {
7766 let serde_json::Value::Object(casts) = operand else {
7767 return Err(async_graphql::Error::new(
7768 "_cast takes an object naming the type to compare as",
7769 ));
7770 };
7771 let mut conditions = Vec::new();
7772 for (target, comparisons) in casts {
7773 let pg_type = match target.as_str() {
7777 "geometry" | "geography" => target.as_str(),
7778 "String" => "text",
7779 _ => {
7780 return Err(async_graphql::Error::new(format!(
7781 "cannot compare \"{}\" as \"{}\"",
7782 column, target
7783 )))
7784 }
7785 };
7786 let serde_json::Value::Object(ops) = comparisons else {
7787 continue;
7788 };
7789 let cast = format!("{}::{}", quoted, pg_type);
7790 for (nested_op, nested_operand) in ops {
7791 conditions.push(comparison_sql(
7792 &cast,
7793 column,
7794 Some(pg_type),
7795 nested_op,
7796 nested_operand,
7797 param_idx,
7798 values,
7799 )?);
7800 }
7801 }
7802 return Ok(match conditions.len() {
7803 0 => "true".to_string(),
7804 1 => conditions.pop().expect("just counted"),
7805 _ => format!("({})", conditions.join(" AND ")),
7806 });
7807 }
7808
7809 if let Some(function) = crate::input::bool_exp::postgis_function(op) {
7812 return postgis_sql(
7813 quoted,
7814 column_type,
7815 function,
7816 op,
7817 operand,
7818 param_idx,
7819 values,
7820 );
7821 }
7822
7823 if let Some((operator, cast)) = crate::input::bool_exp::ltree_operator(op) {
7827 if cast.ends_with("[]") {
7828 let items = operand.as_array().ok_or_else(|| {
7829 async_graphql::Error::new(format!(
7830 "the \"{}\" comparison on \"{}\" takes a list",
7831 op, column
7832 ))
7833 })?;
7834 return Ok(format!(
7835 "{} {} {}",
7836 quoted,
7837 operator,
7838 sql_array(items, cast, param_idx, values)
7839 ));
7840 }
7841 let placeholder = format!("${}", param_idx);
7842 *param_idx += 1;
7843 values.push(operand.clone());
7844 return Ok(format!("{} {} {}::{}", quoted, operator, placeholder, cast));
7845 }
7846
7847 if let Some(function) = match op {
7852 "_jsonb_path_exists" => Some("jsonb_path_exists"),
7853 "_jsonb_path_match" => Some("jsonb_path_match"),
7854 _ => None,
7855 } {
7856 let placeholder = format!("${}", param_idx);
7857 *param_idx += 1;
7858 values.push(operand.clone());
7859 return Ok(format!(
7860 "{}({}, {}::jsonpath)",
7861 function, quoted, placeholder
7862 ));
7863 }
7864
7865 let binary = match op {
7867 "_eq" => Some("="),
7868 "_neq" => Some("<>"),
7869 "_gt" => Some(">"),
7870 "_gte" => Some(">="),
7871 "_lt" => Some("<"),
7872 "_lte" => Some("<="),
7873 "_like" => Some("LIKE"),
7874 "_nlike" => Some("NOT LIKE"),
7875 "_ilike" => Some("ILIKE"),
7876 "_nilike" => Some("NOT ILIKE"),
7877 "_similar" => Some("SIMILAR TO"),
7878 "_nsimilar" => Some("NOT SIMILAR TO"),
7879 "_regex" => Some("~"),
7880 "_nregex" => Some("!~"),
7881 "_iregex" => Some("~*"),
7882 "_niregex" => Some("!~*"),
7883 "_contains" => Some("@>"),
7884 "_contained_in" => Some("<@"),
7885 "_has_key" => Some("?"),
7886 _ => None,
7887 };
7888
7889 if let Some(sql_op) = binary {
7890 let placeholder = format!("${}", param_idx);
7891 *param_idx += 1;
7892 values.push(match op {
7897 "_contains" | "_contained_in" => match operand {
7898 serde_json::Value::String(_)
7899 | serde_json::Value::Number(_)
7900 | serde_json::Value::Bool(_) => serde_json::Value::String(operand.to_string()),
7901 other => other.clone(),
7902 },
7903 _ => operand.clone(),
7904 });
7905 let cast = match op {
7917 "_contains" | "_contained_in" => "::jsonb".to_string(),
7918 "_has_key" => "::text".to_string(),
7919 _ if operand.is_null() => match column_type {
7928 Some(pg_type) => format!("::{}", pg_type),
7929 None => String::new(),
7930 },
7931 _ if operand.is_string() => match column_type {
7942 Some(pg_type) => format!("::{}", pg_type),
7943 None => String::new(),
7944 },
7945 _ => String::new(),
7946 };
7947 return Ok(format!("{} {} {}{}", quoted, sql_op, placeholder, cast));
7948 }
7949
7950 match op {
7951 "_is_null" => Ok(if operand.as_bool().unwrap_or(false) {
7952 format!("{} IS NULL", quoted)
7953 } else {
7954 format!("{} IS NOT NULL", quoted)
7955 }),
7956 "_in" | "_nin" => {
7957 let items = operand.as_array().ok_or_else(|| {
7958 async_graphql::Error::new(format!(
7959 "the \"{}\" comparison on \"{}\" requires a list of values",
7960 op, column
7961 ))
7962 })?;
7963 if items.is_empty() {
7964 return Ok(if op == "_in" { "false" } else { "true" }.to_string());
7967 }
7968 let mut placeholders = Vec::with_capacity(items.len());
7969 for item in items {
7970 let cast = match (item.is_null() || item.is_string(), column_type) {
7973 (true, Some(pg_type)) => format!("::{}", pg_type),
7974 _ => String::new(),
7975 };
7976 placeholders.push(format!("${}{}", param_idx, cast));
7977 values.push(item.clone());
7978 *param_idx += 1;
7979 }
7980 Ok(format!(
7981 "{} {} ({})",
7982 quoted,
7983 if op == "_in" { "IN" } else { "NOT IN" },
7984 placeholders.join(", ")
7985 ))
7986 }
7987 "_has_keys_any" | "_has_keys_all" => {
7988 let items = operand.as_array().ok_or_else(|| {
7989 async_graphql::Error::new(format!(
7990 "the \"{}\" comparison on \"{}\" requires a list of keys",
7991 op, column
7992 ))
7993 })?;
7994 Ok(format!(
7995 "{} {} {}",
7996 quoted,
7997 if op == "_has_keys_any" { "?|" } else { "?&" },
7998 sql_array(items, "text[]", param_idx, values)
7999 ))
8000 }
8001 other => Err(async_graphql::Error::new(format!(
8002 "unsupported comparison \"{}\" on \"{}\"",
8003 other, column
8004 ))),
8005 }
8006}
8007
8008#[allow(clippy::too_many_arguments)] async fn build_order_by_clause(
8018 ctx: &ResolverContext<'_>,
8019 schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
8020 schema_name: &str,
8021 table_name: &str,
8022 type_name: &str,
8023 relationships: &HashMap<String, Vec<RelationshipField>>,
8024 reference: &str,
8025 names: &crate::names::NameOverrides,
8026) -> Result<String, async_graphql::Error> {
8027 let Ok(order_arg) = ctx.args.try_get("order_by") else {
8028 return Ok(String::new());
8029 };
8030
8031 let value = accessor_to_json(&order_arg);
8032 let entries: Vec<&serde_json::Value> = match &value {
8035 serde_json::Value::Array(items) => items.iter().collect(),
8036 serde_json::Value::Object(_) => vec![&value],
8037 serde_json::Value::Null => return Ok(String::new()),
8038 _ => {
8039 return Err(async_graphql::Error::new(
8040 "order_by takes an object or a list of them",
8041 ))
8042 }
8043 };
8044 if entries.is_empty() {
8045 return Ok(String::new());
8046 }
8047
8048 let guard = schema_cache
8049 .get()
8050 .await
8051 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8052 let cache = guard
8053 .as_ref()
8054 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8055 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8056 let table = cache
8057 .get_table(&qi)
8058 .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8059
8060 let mut terms = Vec::new();
8061 let mut alias_counter = 0usize;
8062 for entry in entries {
8063 order_terms_into(
8064 entry,
8065 cache,
8066 relationships,
8067 type_name,
8068 table,
8069 reference,
8070 names,
8071 &mut alias_counter,
8072 &mut terms,
8073 )?;
8074 }
8075
8076 if terms.is_empty() {
8077 return Ok(String::new());
8078 }
8079 Ok(format!(" ORDER BY {}", terms.join(", ")))
8080}
8081
8082#[allow(clippy::too_many_arguments)]
8090fn order_terms_into(
8091 entry: &serde_json::Value,
8092 cache: &SchemaCache,
8093 relationships: &HashMap<String, Vec<RelationshipField>>,
8094 type_name: &str,
8095 table: &postrust_core::schema_cache::Table,
8096 reference: &str,
8097 names: &crate::names::NameOverrides,
8098 alias_counter: &mut usize,
8099 terms: &mut Vec<String>,
8100) -> Result<(), async_graphql::Error> {
8101 let serde_json::Value::Object(map) = entry else {
8102 return Err(async_graphql::Error::new(
8103 "each order_by entry is an object mapping a column to a direction",
8104 ));
8105 };
8106 let available: &[RelationshipField] = relationships
8107 .get(type_name)
8108 .map(|r| r.as_slice())
8109 .unwrap_or(&[]);
8110
8111 for (key, value) in map {
8112 if value.is_null() {
8118 continue;
8119 }
8120
8121 if let Some(name) = value.as_str() {
8123 let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8124 async_graphql::Error::new(format!(
8125 "\"{}\" is not a sort direction; expected one of asc, desc, \
8126 asc_nulls_first, asc_nulls_last, desc_nulls_first, desc_nulls_last",
8127 name
8128 ))
8129 })?;
8130 let column = table_column_for(names, table, key);
8131 if table.get_column(column).is_some() {
8132 terms.push(format!(
8133 "{}.{} {}",
8134 reference,
8135 postrust_sql::escape_ident(column),
8136 sql
8137 ));
8138 continue;
8139 }
8140 let function = names
8143 .computed_source(&table.schema, &table.name, key)
8144 .unwrap_or(key);
8145 if let Some(definition) = table.get_computed_column(function) {
8146 terms.push(format!(
8147 "{} {}",
8148 computed_call(definition, &format!("{}.*", reference), reference),
8149 sql
8150 ));
8151 continue;
8152 }
8153 return Err(async_graphql::Error::new(format!(
8154 "cannot order by unknown column \"{}\" on \"{}\"",
8155 key, table.name
8156 )));
8157 }
8158
8159 if let Some(rel) = key
8161 .strip_suffix("_aggregate")
8162 .and_then(|name| available.iter().find(|r| r.name == name && r.is_list))
8163 {
8164 aggregate_order_terms(value, rel, cache, reference, alias_counter, terms)?;
8165 continue;
8166 }
8167
8168 if let Some(rel) = available.iter().find(|r| r.name == *key && !r.is_list) {
8170 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8171 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8172 if plan.columns.is_empty() {
8173 return Err(async_graphql::Error::new(format!(
8174 "cannot order by \"{}\": it is not reached by a key",
8175 key
8176 )));
8177 }
8178 *alias_counter += 1;
8179 let alias = format!("pgrst_ord_{}", alias_counter);
8180 let quoted_alias = postrust_sql::escape_ident(&alias);
8181 let correlation = plan
8182 .columns
8183 .iter()
8184 .map(|(local, foreign)| {
8185 format!(
8186 "{} = {}.{}",
8187 format_args!("{}.{}", reference, postrust_sql::escape_ident(local)),
8188 quoted_alias,
8189 postrust_sql::escape_ident(foreign)
8190 )
8191 })
8192 .collect::<Vec<_>>()
8193 .join(" AND ");
8194
8195 let target_qi = postrust_core::api_request::QualifiedIdentifier::new(
8196 &plan.foreign_schema,
8197 &plan.foreign_table,
8198 );
8199 let Some(target) = cache.get_table(&target_qi) else {
8200 return Err(async_graphql::Error::new(format!(
8201 "unknown table \"{}\"",
8202 plan.foreign_table
8203 )));
8204 };
8205
8206 let mut nested = Vec::new();
8209 order_terms_into(
8210 value,
8211 cache,
8212 relationships,
8213 &rel.target_type,
8214 target,
8215 "ed_alias,
8216 names,
8217 alias_counter,
8218 &mut nested,
8219 )?;
8220 for term in nested {
8221 let (expression, direction) = term
8222 .rsplit_once(' ')
8223 .map(|(e, d)| (e.to_string(), d.to_string()))
8224 .unwrap_or((term.clone(), String::new()));
8225 terms.push(format!(
8226 "(SELECT {} FROM {}.{} AS {} WHERE {}) {}",
8227 expression,
8228 postrust_sql::escape_ident(&plan.foreign_schema),
8229 postrust_sql::escape_ident(&plan.foreign_table),
8230 quoted_alias,
8231 correlation,
8232 direction
8233 ));
8234 }
8235 continue;
8236 }
8237
8238 return Err(async_graphql::Error::new(format!(
8239 "cannot order by \"{}\" on \"{}\"",
8240 key, table.name
8241 )));
8242 }
8243 Ok(())
8244}
8245
8246fn aggregate_order_terms(
8248 value: &serde_json::Value,
8249 rel: &RelationshipField,
8250 cache: &SchemaCache,
8251 reference: &str,
8252 alias_counter: &mut usize,
8253 terms: &mut Vec<String>,
8254) -> Result<(), async_graphql::Error> {
8255 let serde_json::Value::Object(spec) = value else {
8256 return Err(async_graphql::Error::new(
8257 "ordering by an aggregate takes an object, such as {count: desc}",
8258 ));
8259 };
8260 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8261 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8262 if plan.columns.is_empty() && plan.function.is_none() {
8263 return Err(async_graphql::Error::new(format!(
8264 "cannot order by an aggregate of \"{}\": it is not reached by a key",
8265 rel.name
8266 )));
8267 }
8268 if plan.function.is_some() && !rel.arguments.is_empty() {
8271 return Err(async_graphql::Error::new(format!(
8272 "cannot order by an aggregate of \"{}\": it takes arguments, and an \
8273 ordering has nowhere to write them",
8274 rel.name
8275 )));
8276 }
8277
8278 *alias_counter += 1;
8279 let alias = postrust_sql::escape_ident(&format!("pgrst_ord_{}", alias_counter));
8280 let (source, correlation) = match &plan.function {
8283 Some(function) => {
8284 let session = plan.row_argument.as_ref().and_then(|row_argument| {
8285 computed_session_argument(cache, function, row_argument)
8286 .map(|session| (row_argument.clone(), session))
8287 });
8288 let call = match session {
8289 Some((row_argument, session)) => format!(
8290 "{} => {}, {} => {}",
8291 postrust_sql::escape_ident(&row_argument),
8292 reference,
8293 postrust_sql::escape_ident(&session),
8294 SESSION_ARGUMENT
8295 ),
8296 None => reference.to_string(),
8297 };
8298 (
8299 format!(
8300 "{}.{}({})",
8301 postrust_sql::escape_ident(&function.schema),
8302 postrust_sql::escape_ident(&function.name),
8303 call
8304 ),
8305 "true".to_string(),
8306 )
8307 }
8308 None => (
8309 format!(
8310 "{}.{}",
8311 postrust_sql::escape_ident(&plan.foreign_schema),
8312 postrust_sql::escape_ident(&plan.foreign_table)
8313 ),
8314 plan.columns
8315 .iter()
8316 .map(|(local, foreign)| {
8317 format!(
8318 "{}.{} = {}.{}",
8319 reference,
8320 postrust_sql::escape_ident(local),
8321 alias,
8322 postrust_sql::escape_ident(foreign)
8323 )
8324 })
8325 .collect::<Vec<_>>()
8326 .join(" AND "),
8327 ),
8328 };
8329
8330 let mut wanted: Vec<(String, String)> = Vec::new();
8332 for (function, argument) in spec {
8333 if argument.is_null() {
8336 continue;
8337 }
8338 if function == "count" {
8339 let name = argument.as_str().unwrap_or_default();
8340 let Some(direction) = crate::input::order_by::direction_sql(name) else {
8341 return Err(async_graphql::Error::new(format!(
8342 "\"{}\" is not a sort direction",
8343 name
8344 )));
8345 };
8346 wanted.push(("count(*)".to_string(), direction.to_string()));
8347 continue;
8348 }
8349 let serde_json::Value::Object(columns) = argument else {
8350 continue;
8351 };
8352 for (column, direction) in columns {
8353 if direction.is_null() {
8354 continue;
8355 }
8356 let name = direction.as_str().unwrap_or_default();
8357 let Some(direction) = crate::input::order_by::direction_sql(name) else {
8358 return Err(async_graphql::Error::new(format!(
8359 "\"{}\" is not a sort direction",
8360 name
8361 )));
8362 };
8363 wanted.push((
8366 format!(
8367 "{}({}.{})",
8368 function,
8369 alias,
8370 postrust_sql::escape_ident(column)
8371 ),
8372 direction.to_string(),
8373 ));
8374 }
8375 }
8376
8377 for (expression, direction) in wanted {
8378 terms.push(format!(
8379 "(SELECT {} FROM {} AS {} WHERE {}) {}",
8380 expression, source, alias, correlation, direction
8381 ));
8382 }
8383 Ok(())
8384}
8385
8386fn distinct_on_clause(
8400 distinct_on: &[String],
8401 written: Option<&str>,
8402) -> Result<(String, Option<String>), async_graphql::Error> {
8403 if distinct_on.is_empty() {
8404 return Ok((String::new(), written.map(str::to_string)));
8405 }
8406 let terms: Vec<&str> = written
8407 .map(|written| written.split(", ").collect())
8408 .unwrap_or_default();
8409 let column_of = |term: &str| {
8412 let expression = term.split_whitespace().next().unwrap_or(term);
8413 expression
8414 .rsplit_once('.')
8415 .map(|(_, name)| name)
8416 .unwrap_or(expression)
8417 .to_string()
8418 };
8419 let leading: Vec<String> = terms
8420 .iter()
8421 .take(distinct_on.len())
8422 .map(|term| column_of(term))
8423 .collect();
8424 if !terms.is_empty()
8425 && (leading.len() != distinct_on.len()
8426 || distinct_on
8427 .iter()
8428 .any(|column| !leading.contains(&column_of(column))))
8429 {
8430 return Err(validation_error(
8431 "\"distinct_on\" columns must match initial \"order_by\" columns",
8432 ));
8433 }
8434 let order = match terms.is_empty() {
8435 true => Some(distinct_on.join(", ")),
8436 false => written.map(str::to_string),
8437 };
8438 Ok((format!("DISTINCT ON ({}) ", distinct_on.join(", ")), order))
8439}
8440
8441async fn build_distinct_on(
8449 ctx: &ResolverContext<'_>,
8450 schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
8451 schema_name: &str,
8452 table_name: &str,
8453 names: &crate::names::NameOverrides,
8454) -> Result<Vec<String>, async_graphql::Error> {
8455 let Ok(arg) = ctx.args.try_get("distinct_on") else {
8456 return Ok(Vec::new());
8457 };
8458 let value = accessor_to_json(&arg);
8459 let requested: Vec<String> = match &value {
8460 serde_json::Value::Array(items) => items
8461 .iter()
8462 .filter_map(|i| i.as_str().map(|s| s.to_string()))
8463 .collect(),
8464 serde_json::Value::String(one) => vec![one.clone()],
8465 _ => Vec::new(),
8466 };
8467 if requested.is_empty() {
8468 return Ok(Vec::new());
8469 }
8470
8471 let guard = schema_cache
8472 .get()
8473 .await
8474 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8475 let cache = guard
8476 .as_ref()
8477 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8478 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8479 let table = cache
8480 .get_table(&qi)
8481 .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8482
8483 let mut quoted = Vec::with_capacity(requested.len());
8484 for field in requested {
8485 let name = table_column_for(names, table, &field).to_string();
8486 if table.get_column(&name).is_none() {
8487 return Err(async_graphql::Error::new(format!(
8488 "cannot take distinct on unknown column \"{}\" of \"{}\"",
8489 field, table_name
8490 )));
8491 }
8492 quoted.push(postrust_sql::escape_ident(&name));
8493 }
8494 Ok(quoted)
8495}
8496
8497#[allow(clippy::too_many_arguments)] fn computed_projections<'a>(
8511 table: &postrust_core::schema_cache::Table,
8512 fields: impl IntoIterator<Item = SelectionField<'a>>,
8513 row_reference: &str,
8514 names: &crate::names::NameOverrides,
8515 schema_cache: &SchemaCache,
8516 param_idx: &mut usize,
8517 values: &mut Vec<serde_json::Value>,
8518) -> Result<Vec<String>, async_graphql::Error> {
8519 let mut projections = Vec::new();
8520 for field in fields {
8521 let name = field.name();
8522 if table
8525 .get_column(table_column_for(names, table, name))
8526 .is_some()
8527 {
8528 continue;
8529 }
8530 let function = names
8533 .computed_source(&table.schema, &table.name, name)
8534 .unwrap_or(name);
8535 let Some(computed) = table.get_computed_column(function) else {
8536 continue;
8537 };
8538 let arguments = crate::schema::computed_caller_arguments(computed, schema_cache);
8539 let given = match arguments.is_empty() {
8540 true => None,
8541 false => embed_arguments(field).remove("args"),
8542 };
8543 projections.push(format!(
8544 "{} AS {}",
8545 computed_column_call(
8546 computed,
8547 row_reference,
8548 name,
8549 &arguments,
8550 given.as_ref(),
8551 param_idx,
8552 values,
8553 )?,
8554 postrust_sql::escape_ident(name)
8555 ));
8556 }
8557 Ok(projections)
8558}
8559
8560fn computed_column_call(
8569 computed: &postrust_core::schema_cache::ComputedColumn,
8570 row: &str,
8571 field_name: &str,
8572 arguments: &[(String, String, bool)],
8573 given: Option<&serde_json::Value>,
8574 param_idx: &mut usize,
8575 values: &mut Vec<serde_json::Value>,
8576) -> Result<String, async_graphql::Error> {
8577 if arguments.is_empty() {
8578 return Ok(computed_call(computed, row, row));
8579 }
8580 let Some(row_argument) = &computed.row_argument else {
8581 return Ok(computed_call(computed, row, row));
8582 };
8583 let function = format!(
8584 "{}.{}",
8585 postrust_sql::escape_ident(&computed.function.schema),
8586 postrust_sql::escape_ident(&computed.function.name)
8587 );
8588 let mut passed = vec![format!(
8589 "{} => {}",
8590 postrust_sql::escape_ident(row_argument),
8591 row
8592 )];
8593 if let Some(session) = &computed.session_argument {
8594 passed.push(format!(
8595 "{} => {}",
8596 postrust_sql::escape_ident(session),
8597 SESSION_ARGUMENT
8598 ));
8599 }
8600 for (name, pg_type, required) in arguments {
8601 match given.and_then(|given| given.get(name)) {
8602 None if *required => {
8603 return Err(async_graphql::Error::new(format!(
8604 "{} needs the argument \"{}\"",
8605 field_name, name
8606 )))
8607 }
8608 None => continue,
8610 Some(value) => {
8611 passed.push(format!(
8612 "{} => ${}::{}",
8613 postrust_sql::escape_ident(name),
8614 param_idx,
8615 pg_type
8616 ));
8617 *param_idx += 1;
8618 values.push(value.clone());
8619 }
8620 }
8621 }
8622 Ok(format!("{}({})", function, passed.join(", ")))
8623}
8624
8625fn count_expression(
8637 field: async_graphql::SelectionField<'_>,
8638 table: (&str, &str),
8639 alias: Option<&str>,
8640 names: &crate::names::NameOverrides,
8641) -> String {
8642 let arguments = embed_arguments(field);
8643 let requested = match arguments.get("columns") {
8644 Some(serde_json::Value::Array(items)) => items
8645 .iter()
8646 .filter_map(|item| item.as_str())
8647 .collect::<Vec<_>>(),
8648 Some(serde_json::Value::String(one)) => vec![one.as_str()],
8649 _ => Vec::new(),
8650 };
8651 if requested.is_empty() {
8652 return "count(*)".to_string();
8653 }
8654 let columns: Vec<String> = requested
8655 .into_iter()
8656 .map(|column| {
8657 let (schema, name) = table;
8658 let source = column_for(names, schema, name, column);
8659 match alias {
8660 Some(alias) => format!(
8661 "{}.{}",
8662 postrust_sql::escape_ident(alias),
8663 postrust_sql::escape_ident(source)
8664 ),
8665 None => postrust_sql::escape_ident(source),
8666 }
8667 })
8668 .collect();
8669 let counted = match columns.len() {
8672 1 => columns.into_iter().next().unwrap_or_default(),
8673 _ => format!("({})", columns.join(", ")),
8674 };
8675 let distinct = matches!(
8676 arguments.get("distinct"),
8677 Some(serde_json::Value::Bool(true))
8678 );
8679 match distinct {
8680 true => format!("count(DISTINCT {})", counted),
8681 false => format!("count({})", counted),
8682 }
8683}
8684
8685#[allow(clippy::too_many_arguments)] fn nested_aggregate_select(
8693 caller: &crate::role::Caller<'_>,
8694 selection: async_graphql::SelectionField<'_>,
8695 child_alias: &str,
8696 schema_cache: &SchemaCache,
8697 relationships: &HashMap<String, Vec<RelationshipField>>,
8698 target_type: &str,
8699 child_table: Option<&postrust_core::schema_cache::Table>,
8700 max_rows: Option<i64>,
8701 alias_counter: &mut usize,
8702 param_idx: &mut usize,
8703 values: &mut Vec<serde_json::Value>,
8704 names: &crate::names::NameOverrides,
8705) -> Result<String, async_graphql::Error> {
8706 use crate::schema::aggregate as agg;
8707
8708 let mut parts: Vec<String> = Vec::new();
8709
8710 for child in selection.selection_set() {
8711 match child.name() {
8712 "aggregate" => {
8713 let of_table = child_table
8714 .map(|table| (table.schema.as_str(), table.name.as_str()))
8715 .unwrap_or(("", ""));
8716 let mut counts: Vec<(String, String)> = Vec::new();
8721 let mut wanted: BTreeMap<&str, Vec<String>> = BTreeMap::new();
8722 for function in child.selection_set() {
8723 if function.name() == "count" {
8724 counts.push((
8725 function.alias().unwrap_or("count").to_string(),
8726 count_expression(function, of_table, Some(child_alias), names),
8727 ));
8728 continue;
8729 }
8730 let sql_function = agg::NUMERIC_AGGREGATES
8731 .iter()
8732 .chain(agg::ORDERED_AGGREGATES.iter())
8733 .find(|(name, _)| *name == function.name())
8734 .map(|(name, _)| *name);
8735 let Some(sql_function) = sql_function else {
8736 continue;
8737 };
8738 let columns = wanted.entry(sql_function).or_default();
8739 for column in function.selection_set() {
8740 let name = column.name().to_string();
8741 if name != "__typename" && !columns.contains(&name) {
8743 columns.push(name);
8744 }
8745 }
8746 }
8747 let mut fields = match counts.iter().any(|(key, _)| key == "count") {
8750 true => Vec::new(),
8751 false => vec!["'count', count(*)".to_string()],
8752 };
8753 for (key, expression) in &counts {
8754 fields.push(format!(
8755 "{}, {}",
8756 postrust_sql::quote_literal(key),
8757 expression
8758 ));
8759 }
8760 for (sql_function, named) in &wanted {
8761 let columns: Vec<String> = named
8762 .iter()
8763 .map(|column| {
8764 let source = child_table
8765 .map(|table| table_column_for(names, table, column))
8766 .unwrap_or(column.as_str());
8767 format!(
8768 "{}, {}({}.{})",
8769 postrust_sql::quote_literal(column),
8770 sql_function,
8771 postrust_sql::escape_ident(child_alias),
8772 postrust_sql::escape_ident(source)
8773 )
8774 })
8775 .collect();
8776 fields.push(format!(
8779 "'{}', json_build_object({})",
8780 sql_function,
8781 columns.join(", ")
8782 ));
8783 }
8784 parts.push(format!(
8785 "json_build_object({}) AS {}",
8786 fields.join(", "),
8787 postrust_sql::escape_ident("aggregate")
8788 ));
8789 }
8790 "nodes" => {
8795 let embeds = build_embed_expressions(
8796 caller,
8797 schema_cache,
8798 relationships,
8799 target_type,
8800 child_alias,
8801 child.selection_set(),
8802 max_rows,
8803 alias_counter,
8804 param_idx,
8805 values,
8806 names,
8807 )?;
8808 let mut columns: Vec<String> = Vec::new();
8809 for column in child.selection_set() {
8810 let name = column.name();
8811 if name == "__typename" || embeds.iter().any(|(field, _)| field == name) {
8812 continue;
8813 }
8814 columns.push(format!(
8815 "{}, {}",
8816 postrust_sql::quote_literal(name),
8817 column_expression(child_table, child_alias, name, names)
8818 ));
8819 }
8820 for (field, expression) in &embeds {
8821 columns.push(format!(
8822 "{}, {}",
8823 postrust_sql::quote_literal(field),
8824 expression
8825 ));
8826 }
8827 let row = if columns.is_empty() {
8828 format!("row_to_json({})", postrust_sql::escape_ident(child_alias))
8829 } else {
8830 format!("json_build_object({})", columns.join(", "))
8831 };
8832 parts.push(format!(
8833 "COALESCE(json_agg({}), '[]'::json) AS {}",
8834 row,
8835 postrust_sql::escape_ident("nodes")
8836 ));
8837 }
8838 _ => {}
8839 }
8840 }
8841
8842 if parts.is_empty() {
8843 parts.push("count(*) AS pgrst_empty".to_string());
8846 }
8847 Ok(parts.join(", "))
8848}
8849
8850fn column_expression(
8855 table: Option<&postrust_core::schema_cache::Table>,
8856 alias: &str,
8857 name: &str,
8858 names: &crate::names::NameOverrides,
8859) -> String {
8860 let column = table
8861 .map(|t| table_column_for(names, t, name))
8862 .unwrap_or(name);
8863 let plain = format!(
8864 "{}.{}",
8865 postrust_sql::escape_ident(alias),
8866 postrust_sql::escape_ident(column)
8867 );
8868 let computed = table
8869 .filter(|t| t.get_column(column).is_none())
8870 .and_then(|t| {
8871 let function = names
8872 .computed_source(&t.schema, &t.name, name)
8873 .unwrap_or(name);
8874 t.get_computed_column(function)
8875 });
8876 match computed {
8877 Some(definition) => computed_call(
8878 definition,
8879 &format!("{}.*", postrust_sql::escape_ident(alias)),
8880 &postrust_sql::escape_ident(alias),
8881 ),
8882 None => plain,
8883 }
8884}
8885
8886fn order_terms(
8894 order: &serde_json::Value,
8895 schema_cache: &SchemaCache,
8896 schema_name: &str,
8897 table_name: &str,
8898 alias: &str,
8899 names: &crate::names::NameOverrides,
8900) -> Result<Option<String>, async_graphql::Error> {
8901 let entries: Vec<&serde_json::Value> = match order {
8902 serde_json::Value::Array(items) => items.iter().collect(),
8903 serde_json::Value::Object(_) => vec![order],
8904 _ => return Ok(None),
8905 };
8906
8907 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8908 let table = schema_cache
8909 .get_table(&qi)
8910 .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8911
8912 let mut terms = Vec::new();
8913 for entry in entries {
8914 let serde_json::Value::Object(map) = entry else {
8915 continue;
8916 };
8917 for (field, direction) in map {
8918 if direction.is_null() {
8920 continue;
8921 }
8922 let column = table_column_for(names, table, field);
8923 if table.get_column(column).is_none() {
8924 return Err(async_graphql::Error::new(format!(
8925 "cannot order by unknown column \"{}\" on \"{}\"",
8926 field, table_name
8927 )));
8928 }
8929 let name = direction.as_str().unwrap_or_default();
8930 let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8931 async_graphql::Error::new(format!("\"{}\" is not a sort direction", name))
8932 })?;
8933 terms.push(format!(
8934 "{}.{} {}",
8935 postrust_sql::escape_ident(alias),
8936 postrust_sql::escape_ident(column),
8937 sql
8938 ));
8939 }
8940 }
8941
8942 Ok(if terms.is_empty() {
8943 None
8944 } else {
8945 Some(terms.join(", "))
8946 })
8947}
8948
8949fn always_present(rel: &RelationshipField, table: &postrust_core::schema_cache::Table) -> bool {
8961 use postrust_core::schema_cache::{Cardinality, Relationship};
8962 let Relationship::ForeignKey { cardinality, .. } = &rel.relationship else {
8963 return false;
8964 };
8965 match cardinality {
8966 Cardinality::M2O { .. } => {}
8967 Cardinality::O2O {
8968 is_parent: false, ..
8969 } => {}
8970 _ => return false,
8971 }
8972 let columns = cardinality.columns();
8973 !columns.is_empty()
8974 && columns.iter().all(|(local, _)| {
8975 table
8976 .get_column(local)
8977 .is_some_and(|column| !column.nullable)
8978 })
8979}
8980
8981fn child_holds_the_key(relationship: &postrust_core::schema_cache::Relationship) -> bool {
8989 use postrust_core::schema_cache::{Cardinality, Relationship};
8990 match relationship {
8991 Relationship::ForeignKey { cardinality, .. } => match cardinality {
8992 Cardinality::M2O { .. } => false,
8993 Cardinality::O2O { is_parent, .. } => *is_parent,
8994 _ => true,
8995 },
8996 Relationship::Computed { .. } => true,
8997 }
8998}
8999
9000fn computed_session_argument(
9002 cache: &SchemaCache,
9003 function: &postrust_core::api_request::QualifiedIdentifier,
9004 row_argument: &str,
9005) -> Option<String> {
9006 let _ = row_argument;
9007 cache
9008 .routines
9009 .get(function)
9010 .into_iter()
9011 .flatten()
9012 .find(|routine| routine.name == function.name)?
9013 .params
9014 .iter()
9015 .find(|param| {
9016 param.name == "hasura_session" && matches!(param.param_type.as_str(), "json" | "jsonb")
9017 })
9018 .map(|param| param.name.clone())
9019}
9020
9021fn computed_args_type_name(type_name: &str, field: &str) -> String {
9023 format!("{}_{}_args", type_name, field)
9024}
9025
9026fn computed_arguments(
9033 relationship: &RelationshipField,
9034 plan: &postrust_core::embed::EmbedPlan,
9035 parent_row: &str,
9036 given: Option<&serde_json::Value>,
9037 param_idx: &mut usize,
9038 values: &mut Vec<serde_json::Value>,
9039 schema_cache: &SchemaCache,
9040) -> Result<Option<String>, async_graphql::Error> {
9041 let (Some(function), Some(row_argument)) = (&plan.function, &plan.row_argument) else {
9042 return Ok(None);
9043 };
9044 let session = schema_cache
9045 .routines
9046 .get(function)
9047 .into_iter()
9048 .flatten()
9049 .find(|routine| routine.name == function.name)
9050 .and_then(|routine| {
9051 routine
9052 .params
9053 .iter()
9054 .find(|param| {
9055 param.name == "hasura_session"
9056 && matches!(param.param_type.as_str(), "json" | "jsonb")
9057 })
9058 .map(|param| param.name.clone())
9059 });
9060 if relationship.arguments.is_empty() && session.is_none() {
9061 return Ok(None);
9062 }
9063
9064 let mut passed = vec![format!(
9065 "{} => {}",
9066 postrust_sql::escape_ident(row_argument),
9067 parent_row
9068 )];
9069 if let Some(session) = session {
9070 passed.push(format!(
9071 "{} => {}",
9072 postrust_sql::escape_ident(&session),
9073 SESSION_ARGUMENT
9074 ));
9075 }
9076 for (name, pg_type, required) in &relationship.arguments {
9077 match given.and_then(|given| given.get(name)) {
9078 None if *required => {
9079 return Err(async_graphql::Error::new(format!(
9080 "{} needs the argument \"{}\"",
9081 relationship.name, name
9082 )))
9083 }
9084 None => continue,
9086 Some(value) => {
9087 passed.push(format!(
9088 "{} => ${}::{}",
9089 postrust_sql::escape_ident(name),
9090 param_idx,
9091 pg_type
9092 ));
9093 *param_idx += 1;
9094 values.push(value.clone());
9095 }
9096 }
9097 }
9098 Ok(Some(passed.join(", ")))
9099}
9100
9101fn embed_arguments(field: async_graphql::SelectionField<'_>) -> HashMap<String, serde_json::Value> {
9103 field
9104 .arguments()
9105 .map(|args| {
9106 args.into_iter()
9107 .map(|(name, value)| {
9108 (
9109 name.to_string(),
9110 plain_numbers(value.into_json().unwrap_or(serde_json::Value::Null)),
9111 )
9112 })
9113 .collect()
9114 })
9115 .unwrap_or_default()
9116}
9117
9118type Narrowing = (String, Option<String>, Option<String>, Option<i64>, i64);
9122
9123#[allow(clippy::too_many_arguments)] fn embed_narrowing(
9130 arguments: &HashMap<String, serde_json::Value>,
9131 plan: &postrust_core::embed::EmbedPlan,
9132 schema_cache: &SchemaCache,
9133 relationships: &HashMap<String, Vec<RelationshipField>>,
9134 target_type: &str,
9135 child_alias: &str,
9136 max_rows: Option<i64>,
9137 param_idx: &mut usize,
9138 values: &mut Vec<serde_json::Value>,
9139 names: &crate::names::NameOverrides,
9140 caller: &crate::role::Caller<'_>,
9141) -> Result<Narrowing, async_graphql::Error> {
9142 let permission =
9146 permission_predicate(caller, names, &plan.foreign_schema, &plan.foreign_table)?;
9147 let requested = arguments.get("where").filter(|v| !v.is_null()).cloned();
9148 let child_where = match and_predicates(requested, permission) {
9149 Some(expression) => {
9150 let child_scope = WhereScope::for_alias(
9151 &plan.foreign_schema,
9152 &plan.foreign_table,
9153 child_alias,
9154 target_type,
9155 schema_cache,
9156 relationships,
9157 names,
9158 )
9159 .for_caller(*caller);
9160 let mut nested_alias = 0usize;
9161 build_condition(
9162 &expression,
9163 &child_scope,
9164 param_idx,
9165 values,
9166 &mut nested_alias,
9167 )?
9168 }
9169 None => None,
9170 };
9171
9172 let child_order = match arguments.get("order_by") {
9173 Some(order) if !order.is_null() => order_terms(
9174 order,
9175 schema_cache,
9176 &plan.foreign_schema,
9177 &plan.foreign_table,
9178 child_alias,
9179 names,
9180 )?,
9181 _ => None,
9182 };
9183
9184 let ceiling = match (
9189 max_rows,
9190 crate::role::read_limit(caller, names, &plan.foreign_schema, &plan.foreign_table),
9191 ) {
9192 (Some(configured), Some(granted)) => Some(configured.min(granted)),
9193 (only, None) | (None, only) => only,
9194 };
9195 let child_limit = match arguments.get("limit").and_then(|v| v.as_i64()) {
9196 Some(requested) => match ceiling {
9197 Some(ceiling) => Some(requested.min(ceiling)),
9198 None => Some(requested),
9199 },
9200 None => ceiling,
9201 };
9202 let child_offset = arguments
9203 .get("offset")
9204 .and_then(|v| v.as_i64())
9205 .unwrap_or(0);
9206
9207 let requested = match arguments.get("distinct_on") {
9211 Some(serde_json::Value::Array(items)) => items
9212 .iter()
9213 .filter_map(|item| item.as_str())
9214 .collect::<Vec<_>>(),
9215 Some(serde_json::Value::String(one)) => vec![one.as_str()],
9216 _ => Vec::new(),
9217 };
9218 let mut distinct_on = Vec::with_capacity(requested.len());
9219 if !requested.is_empty() {
9220 let qi = postrust_core::api_request::QualifiedIdentifier::new(
9221 &plan.foreign_schema,
9222 &plan.foreign_table,
9223 );
9224 let table = schema_cache.get_table(&qi).ok_or_else(|| {
9225 async_graphql::Error::new(format!("unknown table \"{}\"", plan.foreign_table))
9226 })?;
9227 for field in requested {
9228 let column = table_column_for(names, table, field).to_string();
9229 if table.get_column(&column).is_none() {
9230 return Err(async_graphql::Error::new(format!(
9231 "cannot take distinct on unknown column \"{}\" of \"{}\"",
9232 field, plan.foreign_table
9233 )));
9234 }
9235 distinct_on.push(format!(
9236 "{}.{}",
9237 postrust_sql::escape_ident(child_alias),
9238 postrust_sql::escape_ident(&column)
9239 ));
9240 }
9241 }
9242 let (distinct, child_order) = distinct_on_clause(&distinct_on, child_order.as_deref())?;
9243
9244 Ok((
9245 distinct,
9246 child_where,
9247 child_order,
9248 child_limit,
9249 child_offset,
9250 ))
9251}
9252
9253#[allow(clippy::too_many_arguments)] fn build_embed_expressions<'a>(
9255 caller: &crate::role::Caller<'_>,
9256 schema_cache: &SchemaCache,
9257 relationships: &HashMap<String, Vec<RelationshipField>>,
9258 type_name: &str,
9259 parent_alias: &str,
9260 fields: impl IntoIterator<Item = SelectionField<'a>>,
9261 max_rows: Option<i64>,
9262 alias_counter: &mut usize,
9263 param_idx: &mut usize,
9264 values: &mut Vec<serde_json::Value>,
9265 names: &crate::names::NameOverrides,
9266) -> Result<Vec<(String, String)>, async_graphql::Error> {
9267 let Some(available) = relationships.get(type_name) else {
9268 return Ok(Vec::new());
9269 };
9270
9271 let mut expressions = Vec::new();
9272
9273 for field in fields {
9274 let aggregate_of = field
9278 .name()
9279 .strip_suffix("_aggregate")
9280 .and_then(|name| available.iter().find(|r| r.name == name && r.is_list));
9281
9282 if let Some(rel) = aggregate_of {
9283 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9284 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9285 *alias_counter += 1;
9286 let child_alias = format!("a{}", alias_counter);
9287
9288 let arguments = embed_arguments(field);
9292 let (child_distinct, child_where, child_order, child_limit, child_offset) =
9293 embed_narrowing(
9294 &arguments,
9295 &plan,
9296 schema_cache,
9297 relationships,
9298 &rel.target_type,
9299 &child_alias,
9300 max_rows,
9301 param_idx,
9302 values,
9303 names,
9304 caller,
9305 )?;
9306
9307 let child_table =
9308 schema_cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
9309 &plan.foreign_schema,
9310 &plan.foreign_table,
9311 ));
9312 let select = nested_aggregate_select(
9313 caller,
9314 field,
9315 &child_alias,
9316 schema_cache,
9317 relationships,
9318 &rel.target_type,
9319 child_table,
9320 max_rows,
9321 alias_counter,
9322 param_idx,
9323 values,
9324 names,
9325 )?;
9326
9327 let call = computed_arguments(
9328 rel,
9329 &plan,
9330 &postrust_sql::escape_ident(parent_alias),
9331 arguments.get("args"),
9332 param_idx,
9333 values,
9334 schema_cache,
9335 )?;
9336 let expression = plan
9337 .aggregate_expression(
9338 parent_alias,
9339 &postrust_sql::escape_ident(parent_alias),
9340 &child_alias,
9341 &select,
9342 child_limit,
9343 child_offset,
9344 child_where.as_deref(),
9345 child_order.as_deref(),
9346 call.as_deref(),
9347 &child_distinct,
9348 postrust_core::EmbedRendering::Verbatim,
9352 )
9353 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9354 expressions.push((field.name().to_string(), expression));
9355 continue;
9356 }
9357
9358 let Some(rel) = available.iter().find(|r| r.name == field.name()) else {
9359 continue;
9360 };
9361
9362 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9363 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9364
9365 *alias_counter += 1;
9366 let child_alias = format!("e{}", alias_counter);
9367
9368 let nested = build_embed_expressions(
9369 caller,
9370 schema_cache,
9371 relationships,
9372 &rel.target_type,
9373 &child_alias,
9374 field.selection_set(),
9375 max_rows,
9376 alias_counter,
9377 param_idx,
9378 values,
9379 names,
9380 )?;
9381
9382 let arguments = embed_arguments(field);
9384 let (child_distinct, child_where, child_order, child_limit, child_offset) =
9385 embed_narrowing(
9386 &arguments,
9387 &plan,
9388 schema_cache,
9389 relationships,
9390 &rel.target_type,
9391 &child_alias,
9392 max_rows,
9393 param_idx,
9394 values,
9395 names,
9396 caller,
9397 )?;
9398
9399 let child_relationships = relationships.get(&rel.target_type);
9402 let child_qi = postrust_core::api_request::QualifiedIdentifier::new(
9403 &plan.foreign_schema,
9404 &plan.foreign_table,
9405 );
9406 let child_table = schema_cache.get_table(&child_qi);
9407 let mut parts: Vec<String> = Vec::new();
9408 for sub in field.selection_set() {
9409 let name = sub.name();
9410 let is_relationship = child_relationships
9411 .map(|rels| {
9412 rels.iter().any(|r| {
9413 r.name == name || (r.is_list && format!("{}_aggregate", r.name) == name)
9414 })
9415 })
9416 .unwrap_or(false);
9417 if is_relationship || name == "__typename" {
9421 continue;
9422 }
9423 let column = child_table
9426 .map(|t| table_column_for(names, t, name))
9427 .unwrap_or(name);
9428 let computed = child_table
9429 .filter(|t| t.get_column(column).is_none())
9430 .and_then(|t| {
9431 let function = names
9432 .computed_source(&t.schema, &t.name, name)
9433 .unwrap_or(name);
9434 t.get_computed_column(function)
9435 });
9436 match computed {
9437 Some(definition) => parts.push(format!(
9438 "{} AS {}",
9439 computed_call(
9440 definition,
9441 &format!("{}.*", postrust_sql::escape_ident(&child_alias)),
9442 &postrust_sql::escape_ident(&child_alias),
9443 ),
9444 postrust_sql::escape_ident(name)
9445 )),
9446 None if column != name => parts.push(format!(
9449 "{} AS {}",
9450 postrust_sql::escape_ident(column),
9451 postrust_sql::escape_ident(name)
9452 )),
9453 None => parts.push(postrust_sql::escape_ident(name)),
9454 }
9455 }
9456 if parts.is_empty() && nested.is_empty() {
9457 parts.push(
9458 child_table
9459 .and_then(|table| rename_projection(table, &child_alias, names))
9460 .unwrap_or_else(|| format!("{}.*", postrust_sql::escape_ident(&child_alias))),
9461 );
9462 }
9463 for (field_name, expression) in nested {
9464 parts.push(format!(
9465 "{} AS {}",
9466 expression,
9467 postrust_sql::escape_ident(&field_name)
9468 ));
9469 }
9470
9471 let computed_call_arguments = computed_arguments(
9472 rel,
9473 &plan,
9474 &postrust_sql::escape_ident(parent_alias),
9475 arguments.get("args"),
9476 param_idx,
9477 values,
9478 schema_cache,
9479 )?;
9480 let expression = plan
9481 .embed_expression(
9482 parent_alias,
9483 &postrust_sql::escape_ident(parent_alias),
9487 &child_alias,
9488 &format!("{}{}", child_distinct, parts.join(", ")),
9489 child_limit,
9490 child_offset,
9491 child_where.as_deref(),
9492 child_order.as_deref(),
9493 computed_call_arguments.as_deref(),
9494 postrust_core::EmbedRendering::Verbatim,
9495 )
9496 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9497
9498 expressions.push((rel.name.clone(), expression));
9499 }
9500
9501 Ok(expressions)
9502}
9503
9504struct EmbedContext<'c> {
9510 schema_cache: &'c SchemaCache,
9511 relationships: &'c HashMap<String, Vec<RelationshipField>>,
9512 max_rows: Option<i64>,
9513}
9514
9515fn embed_relationships<'f>(
9521 conn: &'f mut sqlx::PgConnection,
9522 ctx: &'f EmbedContext<'f>,
9523 type_name: &'f str,
9524 selection: async_graphql::SelectionField<'f>,
9525 rows: &'f mut [serde_json::Value],
9526) -> futures::future::BoxFuture<'f, Result<(), async_graphql::Error>> {
9527 Box::pin(async move {
9528 if rows.is_empty() {
9529 return Ok(());
9530 }
9531
9532 let Some(available) = ctx.relationships.get(type_name) else {
9533 return Ok(());
9534 };
9535
9536 for requested in selection.selection_set() {
9537 let Some(rel) = available.iter().find(|r| r.name == requested.name()) else {
9538 continue;
9539 };
9540
9541 let plan =
9542 postrust_core::embed::EmbedPlan::resolve(&rel.relationship, ctx.schema_cache)
9543 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9544
9545 let keys = postrust_core::embed::parent_keys(rows, &plan.local_column);
9546
9547 let mut child_columns: Vec<String> = Vec::new();
9557 let mut project_everything = false;
9558 let child_relationships = ctx.relationships.get(&rel.target_type);
9559 for field in requested.selection_set() {
9560 let name = field.name();
9561 match child_relationships.and_then(|rels| rels.iter().find(|r| r.name == name)) {
9562 Some(nested_rel) => {
9563 match postrust_core::embed::EmbedPlan::resolve(
9564 &nested_rel.relationship,
9565 ctx.schema_cache,
9566 ) {
9567 Ok(nested_plan) => child_columns.push(nested_plan.local_column),
9568 Err(_) => project_everything = true,
9569 }
9570 }
9571 None if name == "__typename" => {}
9573 None => child_columns.push(name.to_string()),
9574 }
9575 }
9576 if project_everything || child_columns.is_empty() {
9577 child_columns.clear();
9578 }
9579
9580 let mut grouped = if keys.is_empty() {
9581 std::collections::HashMap::new()
9582 } else {
9583 let sql = plan
9584 .children_grouped_sql(ctx.max_rows, &child_columns)
9585 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9586
9587 let fetched = sqlx::query(AssertSqlSafe(sql))
9588 .bind(&keys)
9589 .fetch_all(&mut *conn)
9590 .await
9591 .map_err(database_error)?;
9592
9593 use sqlx::Row;
9596 let pairs: Vec<(serde_json::Value, serde_json::Value)> = fetched
9597 .into_iter()
9598 .filter_map(|row| {
9599 Some((
9600 row.try_get::<serde_json::Value, _>(0).ok()?,
9601 row.try_get::<serde_json::Value, _>(1).ok()?,
9602 ))
9603 })
9604 .collect();
9605
9606 postrust_core::embed::group_from_aggregated(pairs)
9607 };
9608
9609 let has_deeper_embed = ctx
9614 .relationships
9615 .get(&rel.target_type)
9616 .map(|rels| {
9617 requested
9618 .selection_set()
9619 .any(|field| rels.iter().any(|r| r.name == field.name()))
9620 })
9621 .unwrap_or(false);
9622
9623 if has_deeper_embed {
9624 let mut order: Vec<(String, usize)> = Vec::with_capacity(grouped.len());
9625 let mut flat: Vec<serde_json::Value> = Vec::new();
9626 for (key, children) in grouped.drain() {
9627 order.push((key, children.len()));
9628 flat.extend(children);
9629 }
9630
9631 embed_relationships(&mut *conn, ctx, &rel.target_type, requested, &mut flat)
9632 .await?;
9633
9634 let mut rest = flat.into_iter();
9635 for (key, count) in order {
9636 grouped.insert(key, rest.by_ref().take(count).collect());
9637 }
9638 }
9639
9640 for row in rows.iter_mut() {
9641 postrust_core::embed::attach_to_parent(row, &rel.name, &plan, &grouped);
9642 }
9643 }
9644
9645 Ok(())
9646 })
9647}
9648
9649fn pk_where_from_args(
9654 ctx: &ResolverContext<'_>,
9655 schema_name: &str,
9656 table_name: &str,
9657 pk_columns: &[(String, String)],
9658 names: &crate::names::NameOverrides,
9659) -> Result<serde_json::Value, async_graphql::Error> {
9660 if pk_columns.is_empty() {
9661 return Err(async_graphql::Error::new(format!(
9662 "\"{}\" has no primary key, so it cannot be mutated by key",
9663 table_name
9664 )));
9665 }
9666
9667 let from_object = ctx
9671 .args
9672 .try_get("pk_columns")
9673 .ok()
9674 .map(|v| accessor_to_json(&v));
9675
9676 let mut conditions = serde_json::Map::new();
9677 for (col_name, _) in pk_columns {
9678 let field = names
9682 .column(schema_name, table_name, col_name)
9683 .unwrap_or(col_name);
9684 let value = match &from_object {
9685 Some(serde_json::Value::Object(map)) => map.get(field).cloned().ok_or_else(|| {
9686 async_graphql::Error::new(format!(
9687 "pk_columns is missing the key column \"{}\"",
9688 field
9689 ))
9690 })?,
9691 _ => {
9692 let arg = ctx.args.try_get(field).map_err(|_| {
9693 async_graphql::Error::new(format!(
9694 "missing required primary key argument \"{}\"",
9695 field
9696 ))
9697 })?;
9698 accessor_to_json(&arg)
9699 }
9700 };
9701 conditions.insert(field.to_string(), serde_json::json!({ "_eq": value }));
9702 }
9703
9704 Ok(serde_json::Value::Object(conditions))
9705}
9706
9707fn pk_argument_type(pg_type: &str) -> String {
9714 let rendered = crate::types::pg_type_to_graphql(pg_type).to_string();
9715 if rendered.starts_with('[') {
9716 "String".to_string()
9717 } else {
9718 rendered
9719 }
9720}
9721
9722fn exposed_pk_argument_type(
9727 names: &crate::names::NameOverrides,
9728 schema: &str,
9729 table: &str,
9730 column: &str,
9731 pg_type: &str,
9732) -> String {
9733 names
9734 .column_type(schema, table, column)
9735 .map(|given| given.to_string())
9736 .unwrap_or_else(|| pk_argument_type(pg_type))
9737}
9738
9739fn graphql_type_ref(type_str: &str) -> TypeRef {
9741 let is_list = type_str.starts_with('[');
9743 let is_nn = type_str.ends_with('!');
9744
9745 let inner = if is_list {
9747 let stripped = type_str
9748 .trim_end_matches('!') .trim_start_matches('[') .trim_end_matches(']'); stripped
9752 } else {
9753 type_str.trim_end_matches('!')
9754 };
9755
9756 let inner_nn = inner.ends_with('!');
9757 let base_type = inner.trim_end_matches('!');
9758
9759 if is_list {
9760 if is_nn {
9761 if inner_nn {
9762 TypeRef::named_nn_list_nn(base_type)
9763 } else {
9764 TypeRef::named_list_nn(base_type)
9765 }
9766 } else if inner_nn {
9767 TypeRef::named_nn_list(base_type)
9768 } else {
9769 TypeRef::named_list(base_type)
9770 }
9771 } else if is_nn {
9772 TypeRef::named_nn(base_type)
9773 } else {
9774 TypeRef::named(base_type)
9775 }
9776}
9777
9778const NUMBER_AS_TEXT: &str = "$serde_json::private::Number";
9788
9789fn number_kept_as_text(
9792 map: &serde_json::Map<String, serde_json::Value>,
9793) -> Option<serde_json::Value> {
9794 if map.len() != 1 {
9795 return None;
9796 }
9797 let text = map.get(NUMBER_AS_TEXT)?.as_str()?;
9798 serde_json::from_str::<serde_json::Value>(text)
9799 .ok()
9800 .filter(|value| value.is_number())
9801}
9802
9803fn plain_numbers(value: serde_json::Value) -> serde_json::Value {
9805 match value {
9806 serde_json::Value::Object(map) => match number_kept_as_text(&map) {
9807 Some(number) => number,
9808 None => serde_json::Value::Object(
9809 map.into_iter()
9810 .map(|(k, v)| (k, plain_numbers(v)))
9811 .collect(),
9812 ),
9813 },
9814 serde_json::Value::Array(items) => {
9815 serde_json::Value::Array(items.into_iter().map(plain_numbers).collect())
9816 }
9817 other => other,
9818 }
9819}
9820
9821fn accessor_to_json(accessor: &ValueAccessor<'_>) -> serde_json::Value {
9823 if accessor.is_null() {
9825 serde_json::Value::Null
9826 } else if let Ok(b) = accessor.boolean() {
9827 serde_json::Value::Bool(b)
9828 } else if let Ok(i) = accessor.i64() {
9829 serde_json::Value::Number(i.into())
9830 } else if let Ok(f) = accessor.f64() {
9831 serde_json::Number::from_f64(f)
9832 .map(serde_json::Value::Number)
9833 .unwrap_or(serde_json::Value::Null)
9834 } else if let Ok(s) = accessor.string() {
9835 serde_json::Value::String(s.to_string())
9836 } else if let Ok(name) = accessor.enum_name() {
9837 serde_json::Value::String(name.to_string())
9840 } else if let Ok(list) = accessor.list() {
9841 serde_json::Value::Array(list.iter().map(|v| accessor_to_json(&v)).collect())
9842 } else if let Ok(obj) = accessor.object() {
9843 let map: serde_json::Map<String, serde_json::Value> = obj
9844 .iter()
9845 .map(|(k, v)| (k.to_string(), accessor_to_json(&v)))
9846 .collect();
9847 match number_kept_as_text(&map) {
9848 Some(number) => number,
9849 None => serde_json::Value::Object(map),
9850 }
9851 } else {
9852 serde_json::Value::Null
9853 }
9854}
9855
9856fn value_to_json(value: &Value) -> serde_json::Value {
9858 match value {
9859 Value::Null => serde_json::Value::Null,
9860 Value::Boolean(b) => serde_json::Value::Bool(*b),
9861 Value::Number(n) => {
9862 if let Some(i) = n.as_i64() {
9863 serde_json::Value::Number(i.into())
9864 } else if let Some(f) = n.as_f64() {
9865 serde_json::Value::Number(serde_json::Number::from_f64(f).unwrap())
9866 } else {
9867 serde_json::Value::Null
9868 }
9869 }
9870 Value::String(s) => serde_json::Value::String(s.clone()),
9871 Value::List(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
9872 Value::Object(obj) => {
9873 let map: serde_json::Map<String, serde_json::Value> = obj
9874 .iter()
9875 .map(|(k, v)| (k.to_string(), value_to_json(v)))
9876 .collect();
9877 serde_json::Value::Object(map)
9878 }
9879 Value::Binary(b) => serde_json::Value::String(base64::Engine::encode(
9880 &base64::engine::general_purpose::STANDARD,
9881 b,
9882 )),
9883 Value::Enum(e) => serde_json::Value::String(e.to_string()),
9884 }
9885}
9886
9887fn json_to_value(json: serde_json::Value) -> Value {
9889 match json {
9890 serde_json::Value::Null => Value::Null,
9891 serde_json::Value::Bool(b) => Value::Boolean(b),
9892 serde_json::Value::Number(n) => {
9893 if let Some(i) = n.as_i64() {
9894 Value::Number(i.into())
9895 } else if let Some(f) = n.as_f64() {
9896 Value::Number(async_graphql::Number::from_f64(f).unwrap())
9897 } else {
9898 Value::Null
9899 }
9900 }
9901 serde_json::Value::String(s) => Value::String(s),
9902 serde_json::Value::Array(arr) => Value::List(arr.into_iter().map(json_to_value).collect()),
9903 serde_json::Value::Object(obj) => {
9904 let map: indexmap::IndexMap<async_graphql::Name, Value> = obj
9905 .into_iter()
9906 .map(|(k, v)| (async_graphql::Name::new(k), json_to_value(v)))
9907 .collect();
9908 Value::Object(map)
9909 }
9910 }
9911}
9912
9913#[cfg(test)]
9923mod tests {
9924 use super::*;
9925 use indexmap::IndexMap;
9926 use postrust_core::schema_cache::{Cardinality, Column, Relationship, Table};
9927 use postrust_core::QualifiedIdentifier;
9928 use std::collections::{HashMap, HashSet};
9929
9930 fn entity_key_column(pg_type: &str, explicit_id: bool) -> EntityKeyColumn {
9931 EntityKeyColumn {
9932 field_name: "id".to_string(),
9933 column_name: "id".to_string(),
9934 pg_type: pg_type.to_string(),
9935 explicit_id,
9936 }
9937 }
9938
9939 #[test]
9940 fn federation_integer_keys_reject_strings() {
9941 let column = entity_key_column("int4", false);
9942
9943 assert_eq!(
9944 normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap(),
9945 serde_json::json!(2)
9946 );
9947 let error = normalize_entity_key_value("users", &column, &serde_json::json!("2"))
9948 .expect_err("a GraphQL Int cannot be a string");
9949 assert_eq!(
9950 error.message,
9951 "entity representation for \"users\" has invalid key field \"id\": expected a value compatible with PostgreSQL type \"int4\", found a string"
9952 );
9953 }
9954
9955 #[test]
9956 fn federation_bigint_keys_accept_exact_decimal_strings() {
9957 let column = entity_key_column("int8", false);
9958
9959 assert_eq!(
9960 normalize_entity_key_value("users", &column, &serde_json::json!(7)).unwrap(),
9961 serde_json::json!(7)
9962 );
9963 assert_eq!(
9964 normalize_entity_key_value("users", &column, &serde_json::json!("9007199254740993"))
9965 .unwrap(),
9966 serde_json::json!(9_007_199_254_740_993_i64)
9967 );
9968 assert!(normalize_entity_key_value(
9969 "users",
9970 &column,
9971 &serde_json::json!("9223372036854775808")
9972 )
9973 .is_err());
9974 assert!(normalize_entity_key_value("users", &column, &serde_json::json!("7.5")).is_err());
9975 }
9976
9977 #[test]
9978 fn federation_numeric_keys_accept_exact_decimal_strings() {
9979 let column = entity_key_column("numeric", false);
9980
9981 assert_eq!(
9982 normalize_entity_key_value("users", &column, &serde_json::json!(7.5)).unwrap(),
9983 serde_json::json!("7.5")
9984 );
9985 assert_eq!(
9986 normalize_entity_key_value(
9987 "users",
9988 &column,
9989 &serde_json::json!("9007199254740993.123456789")
9990 )
9991 .unwrap(),
9992 serde_json::json!("9007199254740993.123456789")
9993 );
9994 assert!(
9995 normalize_entity_key_value("users", &column, &serde_json::json!("not-a-number"))
9996 .is_err()
9997 );
9998 }
9999
10000 #[test]
10001 fn federation_integer_backed_ids_have_one_normal_form() {
10002 let column = entity_key_column("int4", true);
10003
10004 let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
10005 let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
10006 assert_eq!(number, serde_json::json!(2));
10007 assert_eq!(string, number);
10008 assert!(normalize_entity_key_value("users", &column, &serde_json::json!("two")).is_err());
10009 }
10010
10011 #[test]
10012 fn federation_text_backed_ids_normalize_integer_input_to_text() {
10013 let column = entity_key_column("text", true);
10014
10015 let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
10016 let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
10017 assert_eq!(number, serde_json::json!("2"));
10018 assert_eq!(string, number);
10019 }
10020
10021 #[test]
10022 fn federation_uuid_backed_ids_are_validated_and_canonicalized() {
10023 let column = entity_key_column("uuid", true);
10024
10025 assert_eq!(
10026 normalize_entity_key_value(
10027 "users",
10028 &column,
10029 &serde_json::json!("550E8400-E29B-41D4-A716-446655440000")
10030 )
10031 .unwrap(),
10032 serde_json::json!("550e8400-e29b-41d4-a716-446655440000")
10033 );
10034 assert!(
10035 normalize_entity_key_value("users", &column, &serde_json::json!("not-a-uuid")).is_err()
10036 );
10037 }
10038
10039 fn create_test_table(name: &str) -> Table {
10040 let mut columns = IndexMap::new();
10041 columns.insert(
10042 "id".into(),
10043 Column {
10044 name: "id".into(),
10045 description: None,
10046 nullable: false,
10047 data_type: "integer".into(),
10048 nominal_type: "int4".into(),
10049 max_len: None,
10050 default: Some("nextval('id_seq')".into()),
10051 enum_values: vec![],
10052 is_pk: true,
10053 position: 1,
10054 always_generated: false,
10055 domain_type: None,
10056 },
10057 );
10058 columns.insert(
10059 "name".into(),
10060 Column {
10061 name: "name".into(),
10062 description: None,
10063 nullable: false,
10064 data_type: "text".into(),
10065 nominal_type: "text".into(),
10066 max_len: None,
10067 default: None,
10068 enum_values: vec![],
10069 is_pk: false,
10070 position: 2,
10071 always_generated: false,
10072 domain_type: None,
10073 },
10074 );
10075
10076 Table {
10077 schema: "public".into(),
10078 name: name.into(),
10079 description: None,
10080 is_view: false,
10081 insertable: true,
10082 updatable: true,
10083 deletable: true,
10084 pk_cols: vec!["id".into()],
10085 unique_constraints: Vec::new(),
10086 columns,
10087 computed_columns: Default::default(),
10088 is_partitioned: false,
10089 }
10090 }
10091
10092 fn create_test_schema_cache() -> SchemaCache {
10093 let mut tables = HashMap::new();
10094 let users = create_test_table("users");
10095 tables.insert(users.qualified_identifier(), users);
10096
10097 SchemaCache {
10098 tables,
10099 relationships: HashMap::new(),
10100 routines: HashMap::new(),
10101 timezones: HashSet::new(),
10102 media_handlers: HashMap::new(),
10103 pg_version: 150000,
10104 representations: Default::default(),
10105 }
10106 }
10107
10108 fn add_test_table(cache: &mut SchemaCache, name: &str) {
10109 let table = create_test_table(name);
10110 cache.tables.insert(table.qualified_identifier(), table);
10111 }
10112
10113 fn add_test_posts_relationship(cache: &mut SchemaCache) {
10114 let users = QualifiedIdentifier::new("public", "users");
10115 cache.relationships.insert(
10116 (users.clone(), "public".into()),
10117 vec![Relationship::ForeignKey {
10118 table: users,
10119 foreign_table: QualifiedIdentifier::new("public", "posts"),
10120 is_self: false,
10121 cardinality: Cardinality::O2M {
10122 constraint: "posts_user_id_fkey".into(),
10123 columns: vec![("id".into(), "user_id".into())],
10124 },
10125 table_is_view: false,
10126 foreign_table_is_view: false,
10127 constraint_name: "posts_user_id_fkey".into(),
10128 }],
10129 );
10130 }
10131
10132 #[test]
10137 fn test_graphql_type_ref_simple() {
10138 let _type_ref = graphql_type_ref("String");
10139 }
10141
10142 #[test]
10143 fn test_graphql_type_ref_non_null() {
10144 let _type_ref = graphql_type_ref("String!");
10145 }
10146
10147 #[test]
10148 fn test_graphql_type_ref_list() {
10149 let _type_ref = graphql_type_ref("[String]");
10150 }
10151
10152 #[test]
10153 fn test_graphql_type_ref_list_non_null() {
10154 let _type_ref = graphql_type_ref("[String!]!");
10155 }
10156
10157 #[test]
10162 fn test_value_to_json_null() {
10163 let value = Value::Null;
10164 let json = value_to_json(&value);
10165 assert_eq!(json, serde_json::Value::Null);
10166 }
10167
10168 #[test]
10169 fn test_value_to_json_boolean() {
10170 let value = Value::Boolean(true);
10171 let json = value_to_json(&value);
10172 assert_eq!(json, serde_json::Value::Bool(true));
10173 }
10174
10175 #[test]
10176 fn test_value_to_json_number() {
10177 let value = Value::Number(42.into());
10178 let json = value_to_json(&value);
10179 assert_eq!(json, serde_json::json!(42));
10180 }
10181
10182 #[test]
10183 fn test_value_to_json_string() {
10184 let value = Value::String("hello".to_string());
10185 let json = value_to_json(&value);
10186 assert_eq!(json, serde_json::Value::String("hello".to_string()));
10187 }
10188
10189 #[test]
10190 fn test_value_to_json_list() {
10191 let value = Value::List(vec![Value::Number(1.into()), Value::Number(2.into())]);
10192 let json = value_to_json(&value);
10193 assert_eq!(json, serde_json::json!([1, 2]));
10194 }
10195
10196 #[test]
10197 fn test_json_to_value_null() {
10198 let json = serde_json::Value::Null;
10199 let value = json_to_value(json);
10200 assert!(matches!(value, Value::Null));
10201 }
10202
10203 #[test]
10204 fn test_json_to_value_boolean() {
10205 let json = serde_json::Value::Bool(false);
10206 let value = json_to_value(json);
10207 assert!(matches!(value, Value::Boolean(false)));
10208 }
10209
10210 #[test]
10211 fn test_json_to_value_number() {
10212 let json = serde_json::json!(123);
10213 let value = json_to_value(json);
10214 assert!(matches!(value, Value::Number(_)));
10215 }
10216
10217 #[test]
10218 fn test_json_to_value_string() {
10219 let json = serde_json::Value::String("test".to_string());
10220 let value = json_to_value(json);
10221 assert!(matches!(value, Value::String(_)));
10222 }
10223
10224 #[test]
10225 fn test_json_to_value_array() {
10226 let json = serde_json::json!([1, 2, 3]);
10227 let value = json_to_value(json);
10228 assert!(matches!(value, Value::List(_)));
10229 }
10230
10231 #[test]
10232 fn test_json_to_value_object() {
10233 let json = serde_json::json!({"key": "value"});
10234 let value = json_to_value(json);
10235 assert!(matches!(value, Value::Object(_)));
10236 }
10237
10238 #[test]
10243 fn test_build_dynamic_schema() {
10244 let cache = create_test_schema_cache();
10245 let config = SchemaConfig::default();
10246 let generated = build_schema(&cache, &config);
10247
10248 let result = build_dynamic_schema(
10249 &generated,
10250 &cache,
10251 None,
10252 None,
10253 Arc::new(Default::default()),
10254 std::time::Duration::from_secs(30),
10255 None,
10256 );
10257 if let Err(ref e) = result {
10258 eprintln!("Schema build error: {:?}", e);
10259 }
10260 assert!(result.is_ok(), "Schema build failed: {:?}", result.err());
10261 }
10262
10263 #[test]
10264 fn a_column_type_override_reaches_fields_and_key_arguments() {
10265 let cache = create_test_schema_cache();
10266 let names = crate::names::NameOverrides::parse(
10267 r#"{"tables": {"public.users": {"column_types": {"id": "ID"}}}}"#,
10268 )
10269 .unwrap();
10270 let config = SchemaConfig {
10271 names: names.clone(),
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(names),
10281 std::time::Duration::from_secs(30),
10282 None,
10283 )
10284 .expect("schema with an ID override should build");
10285 let sdl = schema.sdl();
10286
10287 let input_body = |name: &str| {
10288 sdl.split_once(&format!("input {name} {{"))
10289 .and_then(|(_, rest)| rest.split_once('}'))
10290 .map(|(body, _)| body.to_string())
10291 .unwrap_or_else(|| panic!("missing input {name}:\n{sdl}"))
10292 };
10293
10294 assert!(sdl.contains("id: ID!"), "object and input fields:\n{sdl}");
10295 assert!(
10296 sdl.contains("users_by_pk(id: ID!): users"),
10297 "query key argument:\n{sdl}"
10298 );
10299 assert!(
10300 sdl.contains("delete_users_by_pk(id: ID!): users"),
10301 "delete key argument:\n{sdl}"
10302 );
10303 assert!(
10304 input_body("users_pk_columns_input").contains("id: ID!"),
10305 "update key input:\n{sdl}"
10306 );
10307 assert!(
10308 input_body("users_bool_exp").contains("id: ID_comparison_exp"),
10309 "comparison input:\n{sdl}"
10310 );
10311 assert!(
10312 input_body("users_insert_input").contains("id: ID"),
10313 "write input:\n{sdl}"
10314 );
10315 }
10316
10317 #[test]
10318 fn a_type_prefix_names_the_complete_dynamic_schema() {
10319 let cache = create_test_schema_cache();
10320 let config = SchemaConfig {
10321 type_prefix: Some("test".into()),
10322 ..SchemaConfig::default()
10323 };
10324 let generated = build_schema(&cache, &config);
10325 let schema = build_dynamic_schema(
10326 &generated,
10327 &cache,
10328 None,
10329 None,
10330 Arc::new(Default::default()),
10331 std::time::Duration::from_secs(30),
10332 None,
10333 )
10334 .expect("a prefixed schema should build");
10335 let sdl = schema.sdl();
10336
10337 assert!(sdl.contains("type test_users "), "object type:\n{sdl}");
10338 assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10339 assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10340 assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10341 assert!(
10342 sdl.contains("test_users_aggregate"),
10343 "aggregate type:\n{sdl}"
10344 );
10345 assert!(sdl.contains("test_insert_users("), "mutation root:\n{sdl}");
10346 }
10347
10348 #[test]
10349 fn federation_is_absent_by_default() {
10350 let cache = create_test_schema_cache();
10351 let generated = build_schema(&cache, &SchemaConfig::default());
10352 let schema = build_dynamic_schema(
10353 &generated,
10354 &cache,
10355 None,
10356 None,
10357 Arc::new(Default::default()),
10358 std::time::Duration::from_secs(30),
10359 None,
10360 )
10361 .expect("a non-federated schema should build");
10362 let sdl = schema.sdl();
10363
10364 assert!(!sdl.contains("_Service"), "service type:\n{sdl}");
10365 assert!(!sdl.contains("_service"), "service field:\n{sdl}");
10366 assert!(!sdl.contains("@key"), "federation key:\n{sdl}");
10367 }
10368
10369 #[test]
10370 fn federation_entity_selections_respect_fragment_type_conditions() {
10371 let document = async_graphql::parser::parse_query(
10372 r#"
10373 query {
10374 _entities(representations: []) {
10375 __typename
10376 ... on authors { id items { title } }
10377 ...ShopFields
10378 ... { common }
10379 ...EntityName
10380 }
10381 }
10382 fragment ShopFields on shops { id items { sku } }
10383 fragment EntityName on _Entity { __typename }
10384 "#,
10385 )
10386 .expect("query parses");
10387 let (_, operation) = document.operations.iter().next().expect("one operation");
10388 let entity = operation
10389 .node
10390 .selection_set
10391 .node
10392 .items
10393 .iter()
10394 .find_map(|selection| match &selection.node {
10395 Selection::Field(field) if field.node.name.node == "_entities" => Some(&field.node),
10396 _ => None,
10397 })
10398 .expect("_entities field");
10399
10400 assert_eq!(
10401 entity_field_applicability(&entity.selection_set.node, &document.fragments, "authors"),
10402 vec![true, true, true, false, false, true, true]
10403 );
10404 assert_eq!(
10405 entity_field_applicability(&entity.selection_set.node, &document.fragments, "shops"),
10406 vec![true, false, false, true, true, true, true]
10407 );
10408 }
10409
10410 #[tokio::test]
10411 async fn federation_entity_lists_allow_null_members() {
10412 let entity =
10413 Object::new("widgets").field(Field::new("id", TypeRef::named_nn(TypeRef::INT), |_| {
10414 FieldFuture::new(async { Ok(Some(Value::from(2))) })
10415 }));
10416 let union = Union::new("_Entity").possible_type("widgets");
10417 let query = Object::new("Query").field(Field::new(
10418 "_entities",
10419 TypeRef::named_list_nn("_Entity"),
10420 |_| {
10421 FieldFuture::new(async {
10422 Ok(Some(FieldValue::list([
10423 FieldValue::NULL,
10424 FieldValue::NULL.with_type("widgets"),
10425 ])))
10426 })
10427 },
10428 ));
10429 let schema = Schema::build("Query", None, None)
10430 .extension(NullableFederationEntity)
10431 .register(entity)
10432 .register(union)
10433 .register(query)
10434 .finish()
10435 .expect("schema builds");
10436
10437 let response = schema
10438 .execute("{ _entities { ... on widgets { id } } }")
10439 .await;
10440
10441 assert!(response.errors.is_empty(), "{:?}", response.errors);
10442 assert_eq!(
10443 response.data,
10444 async_graphql::value!({ "_entities": [null, { "id": 2 }] })
10445 );
10446 }
10447
10448 #[test]
10449 fn federation_metadata_is_byte_for_byte_inert_when_disabled() {
10450 let cache = create_test_schema_cache();
10451 let sdl_for = |config: SchemaConfig| {
10452 let generated = build_schema(&cache, &config);
10453 build_dynamic_schema(
10454 &generated,
10455 &cache,
10456 None,
10457 None,
10458 Arc::new(config.names.clone()),
10459 std::time::Duration::from_secs(30),
10460 None,
10461 )
10462 .expect("schema should build")
10463 .sdl()
10464 };
10465
10466 let baseline = sdl_for(SchemaConfig {
10467 type_prefix: Some("test".into()),
10468 ..SchemaConfig::default()
10469 });
10470 let with_disabled_federation_metadata = sdl_for(SchemaConfig {
10471 enable_federation: false,
10472 type_prefix: Some("test".into()),
10473 names: crate::names::NameOverrides::parse(
10474 r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10475 )
10476 .expect("metadata"),
10477 ..SchemaConfig::default()
10478 });
10479
10480 assert_eq!(with_disabled_federation_metadata, baseline);
10481 assert!(
10482 with_disabled_federation_metadata.contains("type test_users "),
10483 "prefixed object type remains when federation is disabled:\n{with_disabled_federation_metadata}"
10484 );
10485 assert!(
10486 !with_disabled_federation_metadata.contains("_Service"),
10487 "service type:\n{with_disabled_federation_metadata}"
10488 );
10489 assert!(
10490 !with_disabled_federation_metadata.contains("@key"),
10491 "federation key:\n{with_disabled_federation_metadata}"
10492 );
10493 }
10494
10495 #[test]
10496 fn shared_entities_make_relationship_fields_shareable() {
10497 let federation_sdl = |shared: bool| {
10498 let mut cache = create_test_schema_cache();
10499 add_test_table(&mut cache, "posts");
10500 add_test_posts_relationship(&mut cache);
10501 let names = match shared {
10502 true => crate::names::NameOverrides::parse(
10503 r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10504 )
10505 .expect("metadata"),
10506 false => crate::names::NameOverrides::default(),
10507 };
10508 let config = SchemaConfig {
10509 enable_federation: true,
10510 type_prefix: Some("test".into()),
10511 names: names.clone(),
10512 ..SchemaConfig::default()
10513 };
10514 let generated = build_schema(&cache, &config);
10515 build_dynamic_schema(
10516 &generated,
10517 &cache,
10518 None,
10519 None,
10520 Arc::new(names),
10521 std::time::Duration::from_secs(30),
10522 None,
10523 )
10524 .expect("federated schema should build")
10525 .sdl_with_options(async_graphql::SDLExportOptions::new().federation())
10526 };
10527
10528 let shared = federation_sdl(true);
10529 assert!(
10530 shared.contains("): [test_posts!]! @shareable"),
10531 "shared relationship field:\n{shared}"
10532 );
10533 assert!(
10534 shared.contains("): test_posts_aggregate! @shareable"),
10535 "shared relationship aggregate field:\n{shared}"
10536 );
10537
10538 let not_shared = federation_sdl(false);
10539 assert!(
10540 not_shared.contains("): [test_posts!]!"),
10541 "non-shared relationship field:\n{not_shared}"
10542 );
10543 assert!(
10544 !not_shared.contains("): [test_posts!]! @shareable"),
10545 "non-shared relationship field must not be shareable:\n{not_shared}"
10546 );
10547 assert!(
10548 not_shared.contains("): test_posts_aggregate!"),
10549 "non-shared relationship aggregate field:\n{not_shared}"
10550 );
10551 assert!(
10552 !not_shared.contains("): test_posts_aggregate! @shareable"),
10553 "non-shared relationship aggregate must not be shareable:\n{not_shared}"
10554 );
10555 }
10556
10557 #[tokio::test]
10558 async fn federation_exposes_service_sdl_and_entity_keys() {
10559 let mut cache = create_test_schema_cache();
10560 add_test_table(&mut cache, "posts");
10561 let config = SchemaConfig {
10562 enable_federation: true,
10563 type_prefix: Some("test".into()),
10564 names: crate::names::NameOverrides::parse(
10565 r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10566 )
10567 .expect("metadata"),
10568 ..SchemaConfig::default()
10569 };
10570 let generated = build_schema(&cache, &config);
10571 let schema = build_dynamic_schema(
10572 &generated,
10573 &cache,
10574 None,
10575 None,
10576 Arc::new(Default::default()),
10577 std::time::Duration::from_secs(30),
10578 None,
10579 )
10580 .expect("a federated schema should build");
10581 let sdl = schema.sdl();
10582 let federation_sdl =
10583 schema.sdl_with_options(async_graphql::SDLExportOptions::new().federation());
10584
10585 assert!(sdl.contains("_Service"), "service type:\n{sdl}");
10586 assert!(sdl.contains("_service"), "service field:\n{sdl}");
10587 assert!(
10588 federation_sdl.contains("type users @key(fields: \"id\")"),
10589 "users key:\n{federation_sdl}"
10590 );
10591 assert!(
10592 federation_sdl.contains("@shareable"),
10593 "shared field directive:\n{federation_sdl}"
10594 );
10595 assert!(sdl.contains("type users "), "shared object type:\n{sdl}");
10596 assert!(
10597 !sdl.contains("type test_users "),
10598 "prefixed shared object type:\n{sdl}"
10599 );
10600 assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10601 assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10602 assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10603 assert!(
10604 !sdl.contains("input users_bool_exp "),
10605 "unprefixed derived input:\n{sdl}"
10606 );
10607 assert!(
10608 sdl.contains("test_users_aggregate"),
10609 "aggregate type:\n{sdl}"
10610 );
10611 assert!(
10612 !sdl.contains("type users_aggregate "),
10613 "unprefixed aggregate type:\n{sdl}"
10614 );
10615 assert!(
10616 sdl.contains("test_users_mutation_response"),
10617 "mutation response type:\n{sdl}"
10618 );
10619 assert!(
10620 !sdl.contains("type users_mutation_response "),
10621 "unprefixed mutation response type:\n{sdl}"
10622 );
10623 assert!(
10624 sdl.contains("type test_posts {"),
10625 "non-shared object type remains prefixed:\n{sdl}"
10626 );
10627 assert!(
10628 !sdl.contains("type posts {"),
10629 "non-shared object type must not become unprefixed:\n{sdl}"
10630 );
10631 assert!(sdl.contains("test_posts("), "prefixed posts root:\n{sdl}");
10632 assert!(
10633 sdl.contains("test_posts_bool_exp"),
10634 "prefixed posts bool_exp:\n{sdl}"
10635 );
10636
10637 let response = schema.execute("{ _service { sdl } }").await;
10638 assert!(
10639 response.errors.is_empty(),
10640 "service query errors: {:?}",
10641 response.errors
10642 );
10643 let service = response.data.into_json().expect("service response JSON");
10644 let service_sdl = service
10645 .get("_service")
10646 .and_then(|value| value.get("sdl"))
10647 .and_then(serde_json::Value::as_str)
10648 .expect("service SDL string");
10649 assert!(
10650 service_sdl.contains("type Query"),
10651 "federation root type:\n{service_sdl}"
10652 );
10653 assert!(
10654 service_sdl.contains("test_users("),
10655 "query fields are on the federation root:\n{service_sdl}"
10656 );
10657 assert!(
10658 service_sdl.contains("test_posts("),
10659 "non-shared query fields are on the federation root:\n{service_sdl}"
10660 );
10661 assert!(
10662 !service_sdl.contains("type query_root"),
10663 "Apollo composition treats query_root as an ordinary object without a schema definition:\n{service_sdl}"
10664 );
10665 assert!(
10666 service_sdl.contains("type users @key(fields: \"id\")"),
10667 "{service_sdl}"
10668 );
10669 assert!(
10670 service_sdl.contains("type test_posts @key(fields: \"id\")"),
10671 "{service_sdl}"
10672 );
10673 }
10674
10675 #[test]
10678 fn exists_is_a_subselect_over_another_table() {
10679 let cache = create_test_schema_cache();
10680 let names = crate::names::NameOverrides::default();
10681 let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10682 let scope = WhereScope::table("public", "users", "users", &names)
10683 .with_resolution(&cache, &relationships);
10684
10685 let mut param_idx = 1usize;
10686 let mut values: Vec<serde_json::Value> = Vec::new();
10687 let mut aliases = 0usize;
10688 let sql = build_condition(
10689 &serde_json::json!({
10690 "_exists": {"_table": "users", "_where": {"name": {"_eq": "Ann"}}}
10691 }),
10692 &scope,
10693 &mut param_idx,
10694 &mut values,
10695 &mut aliases,
10696 )
10697 .expect("compiles")
10698 .expect("constrains something");
10699
10700 assert_eq!(
10701 sql,
10702 "EXISTS (SELECT 1 FROM \"public\".\"users\" AS \"pgrst_exists_1\" \
10703 WHERE \"pgrst_exists_1\".\"name\" = $1::text)"
10704 );
10705 assert_eq!(values, vec![serde_json::json!("Ann")]);
10706 }
10707
10708 #[test]
10710 fn exists_names_a_table_the_server_has() {
10711 let cache = create_test_schema_cache();
10712 let names = crate::names::NameOverrides::default();
10713 let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10714 let scope = WhereScope::table("public", "users", "users", &names)
10715 .with_resolution(&cache, &relationships);
10716 let error = build_condition(
10717 &serde_json::json!({"_exists": {"_table": "nowhere", "_where": {}}}),
10718 &scope,
10719 &mut 1,
10720 &mut Vec::new(),
10721 &mut 0,
10722 )
10723 .expect_err("a table that is not there is an error");
10724 assert!(
10725 error.message.contains("public.nowhere"),
10726 "{}",
10727 error.message
10728 );
10729 }
10730
10731 #[test]
10738 fn a_table_a_role_may_count_and_not_read_has_a_count_and_no_rows() {
10739 let cache = create_test_schema_cache();
10740 let names = crate::names::NameOverrides::parse(
10741 r#"{"tables": {"public.users": {"permissions":
10742 {"counter": {"select": {"columns": [], "filter": {},
10743 "allow_aggregations": true}}}}}}"#,
10744 )
10745 .unwrap();
10746 let view = crate::role::cache_for_role(&cache, &names, "counter", false);
10747 assert!(!view.tables.is_empty(), "the table is there to be counted");
10748
10749 let config = SchemaConfig {
10750 names: names.clone(),
10751 role: Some("counter".to_string()),
10752 ..SchemaConfig::default()
10753 };
10754 let generated = build_schema(&view, &config);
10755 let schema = build_dynamic_schema(
10756 &generated,
10757 &view,
10758 None,
10759 None,
10760 Arc::new(names),
10761 std::time::Duration::from_secs(30),
10762 Some("counter"),
10763 )
10764 .expect("a role that may only count still has a buildable schema");
10765 let sdl = schema.sdl();
10766
10767 assert!(
10768 sdl.contains("users_aggregate"),
10769 "the count is there:\n{}",
10770 sdl
10771 );
10772 assert!(sdl.contains("count: Int!"), "and it is a count:\n{}", sdl);
10773 assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10775 assert!(
10776 !sdl.contains("nodes"),
10777 "and no rows beside the numbers:\n{}",
10778 sdl
10779 );
10780 assert!(
10781 !sdl.contains("users_select_column"),
10782 "no column enum to order by or count over:\n{}",
10783 sdl
10784 );
10785 assert!(!sdl.contains("users_max_fields"), "no max:\n{}", sdl);
10787 }
10788
10789 #[test]
10796 fn a_table_a_role_may_only_write_gets_the_write_and_no_type() {
10797 let cache = create_test_schema_cache();
10798 let names = crate::names::NameOverrides::parse(
10799 r#"{"tables": {"public.users": {"permissions":
10800 {"writer": {"insert": {"columns": "*", "check": {}}}}}}}"#,
10801 )
10802 .unwrap();
10803 let view = crate::role::cache_for_role(&cache, &names, "writer", false);
10804 let config = SchemaConfig {
10805 names: names.clone(),
10806 role: Some("writer".to_string()),
10807 ..SchemaConfig::default()
10808 };
10809 let generated = build_schema(&view, &config);
10810
10811 let schema = build_dynamic_schema(
10812 &generated,
10813 &view,
10814 None,
10815 None,
10816 Arc::new(names),
10817 std::time::Duration::from_secs(30),
10818 Some("writer"),
10819 )
10820 .expect("a role that may only write still has a buildable schema");
10821 let sdl = schema.sdl();
10822
10823 assert!(
10824 sdl.contains("insert_users("),
10825 "the write is there:\n{}",
10826 sdl
10827 );
10828 assert!(
10829 sdl.contains("users_insert_input"),
10830 "and its input:\n{}",
10831 sdl
10832 );
10833 assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10835 assert!(!sdl.contains("insert_users_one"), "no insert_one:\n{}", sdl);
10836 assert!(
10837 !sdl.contains("returning"),
10838 "and nothing to return:\n{}",
10839 sdl
10840 );
10841 assert!(
10842 sdl.contains("users_mutation_response"),
10843 "the count is still answered:\n{}",
10844 sdl
10845 );
10846 assert!(
10847 !sdl.contains("_mutation_response_mutation_response"),
10848 "and no type is named twice over:\n{}",
10849 sdl
10850 );
10851 }
10852
10853 fn cache_with_a_mutation_function() -> SchemaCache {
10856 use postrust_core::schema_cache::{FuncVolatility, RetType, Routine};
10857
10858 let mut cache = create_test_schema_cache();
10859 let routine = Routine {
10860 schema: "public".into(),
10861 name: "add_to_score".into(),
10862 description: None,
10863 params: Vec::new(),
10864 return_type: RetType::SetOf("users".into()),
10865 returns_composite: true,
10866 volatility: FuncVolatility::Volatile,
10867 has_variadic: false,
10868 isolation_level: None,
10869 settings: Vec::new(),
10870 is_procedure: false,
10871 media_type: None,
10872 media_base_type: None,
10873 output_columns: Vec::new(),
10874 };
10875 cache
10876 .routines
10877 .insert(routine.qualified_identifier(), vec![routine]);
10878 cache
10879 }
10880
10881 #[test]
10886 fn a_mutation_function_needs_the_role_to_be_named() {
10887 let cache = cache_with_a_mutation_function();
10888 let withheld = crate::names::NameOverrides::parse(
10889 r#"{"tables": {"public.users": {"permissions":
10890 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10891 "functions": {"public.add_to_score": {"exposed_as": "mutation"}}}"#,
10892 )
10893 .unwrap();
10894 let granted = crate::names::NameOverrides::parse(
10895 r#"{"tables": {"public.users": {"permissions":
10896 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10897 "functions": {"public.add_to_score":
10898 {"exposed_as": "mutation", "roles": ["reader"]}}}"#,
10899 )
10900 .unwrap();
10901
10902 let sdl_for = |names: &crate::names::NameOverrides| {
10903 let view = crate::role::cache_for_role(&cache, names, "reader", false);
10904 let config = SchemaConfig {
10905 names: names.clone(),
10906 role: Some("reader".to_string()),
10907 ..SchemaConfig::default()
10908 };
10909 let generated = build_schema(&view, &config);
10910 build_dynamic_schema(
10911 &generated,
10912 &view,
10913 None,
10914 None,
10915 Arc::new(names.clone()),
10916 std::time::Duration::from_secs(30),
10917 Some("reader"),
10918 )
10919 .expect("schema builds")
10920 .sdl()
10921 };
10922
10923 let without = sdl_for(&withheld);
10924 assert!(
10925 !without.contains("add_to_score"),
10926 "not granted, so not there:\n{}",
10927 without
10928 );
10929 assert!(
10932 !without.contains("mutation: mutation_root"),
10933 "and no mutation root at all:\n{}",
10934 without
10935 );
10936
10937 let with = sdl_for(&granted);
10938 assert!(
10939 with.contains("add_to_score"),
10940 "named by a function permission, so it is:\n{}",
10941 with
10942 );
10943 }
10944
10945 #[test]
10947 fn a_role_the_document_never_names_is_granted_nothing() {
10948 let cache = Arc::new(create_test_schema_cache());
10949 let config = SchemaConfig {
10950 names: crate::names::NameOverrides::parse(
10951 r#"{"tables": {"public.users": {"permissions":
10952 {"reader": {"select": {"columns": "*", "filter": {}}}}}}}"#,
10953 )
10954 .unwrap(),
10955 ..SchemaConfig::default()
10956 };
10957 let empty = build_ungranted_schema(&cache, &config).expect("it builds");
10958 let sdl = empty.sdl();
10959 assert!(
10960 sdl.contains("no_queries_available"),
10961 "nothing to read:\n{}",
10962 sdl
10963 );
10964 assert!(!sdl.contains("type users "), "and no table:\n{}", sdl);
10965 assert!(build_ungranted_schema(&cache, &SchemaConfig::default()).is_none());
10967 }
10968
10969 #[test]
10972 fn a_column_a_role_may_set_without_seeing_is_in_the_input_alone() {
10973 let cache = create_test_schema_cache();
10974 let names = crate::names::NameOverrides::parse(
10975 r#"{"tables": {"public.users": {"permissions":
10976 {"user": {"select": {"columns": ["id"], "filter": {}},
10977 "insert": {"columns": "*", "check": {}}}}}}}"#,
10978 )
10979 .unwrap();
10980 let view = crate::role::cache_for_role(&cache, &names, "user", false);
10981 let config = SchemaConfig {
10982 names: names.clone(),
10983 role: Some("user".to_string()),
10984 ..SchemaConfig::default()
10985 };
10986 let generated = build_schema(&view, &config);
10987 let object = &generated.object_types["users"];
10988 assert_eq!(
10989 object
10990 .fields
10991 .iter()
10992 .map(|f| f.name.as_str())
10993 .collect::<Vec<_>>(),
10994 vec!["id"],
10995 "the type shows what may be read"
10996 );
10997 assert!(
10998 object.writable_fields.iter().any(|f| f.name == "name"),
10999 "and the input keeps what may be written"
11000 );
11001
11002 let schema = build_dynamic_schema(
11003 &generated,
11004 &view,
11005 None,
11006 None,
11007 Arc::new(names),
11008 std::time::Duration::from_secs(30),
11009 Some("user"),
11010 )
11011 .expect("schema builds");
11012 let sdl = schema.sdl();
11013 let input = sdl
11014 .split("input users_insert_input")
11015 .nth(1)
11016 .and_then(|rest| rest.split('}').next())
11017 .expect("the insert input is registered");
11018 assert!(input.contains("name:"), "settable without being readable");
11019 }
11020
11021 #[test]
11025 fn a_generated_column_is_readable_and_not_writable() {
11026 let mut cache = create_test_schema_cache();
11027 cache
11028 .tables
11029 .get_mut(&postrust_core::api_request::QualifiedIdentifier::new(
11030 "public", "users",
11031 ))
11032 .expect("the fixture has users")
11033 .columns
11034 .get_mut("id")
11035 .expect("with an id")
11036 .always_generated = true;
11037
11038 let names = crate::names::NameOverrides::default();
11039 let config = SchemaConfig {
11040 names: names.clone(),
11041 ..SchemaConfig::default()
11042 };
11043 let generated = build_schema(&cache, &config);
11044 let schema = build_dynamic_schema(
11045 &generated,
11046 &cache,
11047 None,
11048 None,
11049 Arc::new(names),
11050 std::time::Duration::from_secs(30),
11051 None,
11052 )
11053 .expect("schema builds");
11054 let sdl = schema.sdl();
11055
11056 let block = |header: &str| {
11057 sdl.split(header)
11058 .nth(1)
11059 .and_then(|rest| rest.split('}').next())
11060 .unwrap_or_else(|| panic!("{} is registered:\n{}", header, sdl))
11061 .to_string()
11062 };
11063 assert!(
11064 block("type users ").contains("id:"),
11065 "still readable:\n{}",
11066 sdl
11067 );
11068 for header in ["input users_insert_input", "input users_set_input"] {
11069 assert!(
11070 !block(header).contains("id:"),
11071 "{} may not name it:\n{}",
11072 header,
11073 block(header)
11074 );
11075 }
11076 assert!(
11079 !sdl.contains("users_inc_input"),
11080 "and nothing is left to increment:\n{}",
11081 sdl
11082 );
11083 assert!(
11084 block("input users_insert_input").contains("name:"),
11085 "and the rest of the table is untouched",
11086 );
11087 }
11088
11089 #[test]
11090 fn test_create_object_type() {
11091 let table = create_test_table("users");
11092 let obj = TableObjectType::from_table(&table);
11093 let _gql_obj = create_object_type(&obj, &[], None);
11094 }
11095
11096 #[test]
11097 fn test_create_query_type() {
11098 let cache = create_test_schema_cache();
11099 let config = SchemaConfig::default();
11100 let generated = build_schema(&cache, &config);
11101
11102 let _query = create_query_type(
11103 &generated,
11104 "query_root",
11105 None,
11106 Arc::new(HashMap::new()),
11107 Arc::new(Default::default()),
11108 );
11109 }
11110
11111 #[test]
11112 fn test_create_mutation_type() {
11113 let cache = create_test_schema_cache();
11114 let config = SchemaConfig::default();
11115 let generated = build_schema(&cache, &config);
11116
11117 let _mutation = create_mutation_type(
11118 &generated,
11119 "mutation_root",
11120 Arc::new(HashMap::new()),
11121 Arc::new(HashMap::new()),
11122 Arc::new(Default::default()),
11123 None,
11124 None,
11125 );
11126 }
11127
11128 #[test]
11133 fn a_scalar_is_named_the_way_a_client_declares_it() {
11134 use crate::types::{pg_type_to_graphql, GraphQLType};
11135
11136 assert_eq!(pg_type_to_graphql("jsonb").to_string(), "jsonb");
11141 assert_eq!(pg_type_to_graphql("json").to_string(), "json");
11142 assert_eq!(pg_type_to_graphql("int8").to_string(), "bigint");
11143 assert_eq!(pg_type_to_graphql("numeric").to_string(), "numeric");
11144 assert_eq!(pg_type_to_graphql("timestamptz").to_string(), "timestamptz");
11145 assert_eq!(pg_type_to_graphql("timestamp").to_string(), "timestamp");
11146 assert_eq!(pg_type_to_graphql("uuid").to_string(), "uuid");
11147
11148 assert_eq!(pg_type_to_graphql("geometry").to_string(), "geometry");
11151 assert_eq!(
11152 pg_type_to_graphql("colors_enum"),
11153 GraphQLType::Custom("colors_enum".to_string())
11154 );
11155
11156 assert_eq!(
11158 leaf_scalar_name(&pg_type_to_graphql("_text")),
11159 "String".to_string()
11160 );
11161 }
11162
11163 #[test]
11168 fn every_table_gets_a_boolean_expression() {
11169 let cache = create_test_schema_cache();
11170 let config = SchemaConfig::default();
11171 let generated = build_schema(&cache, &config);
11172
11173 let (inputs, _) = crate::input::bool_exp::build_inputs(
11174 &generated.object_types,
11175 &generated.relationship_fields,
11176 );
11177 let names: HashSet<String> = inputs.iter().map(|i| i.type_name().to_string()).collect();
11178
11179 for table in generated.object_types.keys() {
11180 let expected = format!("{}_bool_exp", table);
11181 assert!(
11182 names.contains(&expected),
11183 "no {} among {:?}",
11184 expected,
11185 names
11186 );
11187 }
11188 assert!(names.contains("String_comparison_exp"));
11190 assert!(names.contains("Int_comparison_exp"));
11191 }
11192
11193 #[test]
11194 fn a_schema_carrying_the_boolean_expressions_still_builds() {
11195 let cache = create_test_schema_cache();
11196 let config = SchemaConfig::default();
11197 let generated = build_schema(&cache, &config);
11198
11199 let schema = build_dynamic_schema(
11200 &generated,
11201 &cache,
11202 None,
11203 None,
11204 Arc::new(Default::default()),
11205 std::time::Duration::from_secs(30),
11206 None,
11207 );
11208 assert!(schema.is_ok(), "{:?}", schema.err());
11209
11210 let sdl = schema.unwrap().sdl();
11211 assert!(
11212 sdl.contains("_bool_exp"),
11213 "no boolean expressions in:\n{}",
11214 sdl
11215 );
11216 assert!(sdl.contains("_and"), "no _and in the generated expressions");
11217 }
11218
11219 #[test]
11224 fn test_build_schema_with_subscriptions() {
11225 let cache = create_test_schema_cache();
11226 let config = SchemaConfig {
11227 enable_subscriptions: true,
11228 ..SchemaConfig::default()
11229 };
11230 let generated = build_schema(&cache, &config);
11231
11232 let sub_fields = generate_subscription_fields(&cache, &generated);
11234 assert!(!sub_fields.is_empty(), "Should have subscription fields");
11235
11236 let result = build_dynamic_schema(
11238 &generated,
11239 &cache,
11240 Some(&sub_fields),
11241 None,
11242 Arc::new(Default::default()),
11243 std::time::Duration::from_secs(30),
11244 None,
11245 );
11246 assert!(result.is_ok(), "Schema with subscriptions should build");
11247 }
11248
11249 #[test]
11250 fn test_subscription_field_generation() {
11251 let cache = create_test_schema_cache();
11252 let config = SchemaConfig::default();
11253 let generated = build_schema(&cache, &config);
11254
11255 let fields = generate_subscription_fields(&cache, &generated);
11256
11257 assert_eq!(fields.len(), 1);
11259 assert_eq!(fields[0].name, "users");
11260 assert_eq!(fields[0].table_name, "users");
11261 assert_eq!(fields[0].channel_name(), "postrust_public_users");
11262 }
11263
11264 #[test]
11265 fn test_create_subscription_type() {
11266 let cache = create_test_schema_cache();
11267 let config = SchemaConfig::default();
11268 let generated = build_schema(&cache, &config);
11269 let _subscription = create_subscription_type(
11272 &generated,
11273 "subscription_root",
11274 None,
11275 Arc::new(Default::default()),
11276 Arc::new(Default::default()),
11277 std::time::Duration::from_secs(30),
11278 );
11279 }
11280}
11281
11282#[derive(Debug, PartialEq, Eq)]
11284enum PathStep {
11285 Key(String),
11286 Index(usize),
11287}
11288
11289fn walk_json_path(value: &Value, path: &str) -> Result<Value, async_graphql::Error> {
11296 let mut at = value;
11297 for step in parse_json_path(path)? {
11298 let next = match (&step, at) {
11299 (PathStep::Key(key), Value::Object(map)) => map.get(key.as_str()),
11300 (PathStep::Index(index), Value::List(items)) => items.get(*index),
11301 _ => None,
11304 };
11305 match next {
11306 Some(next) => at = next,
11307 None => return Ok(Value::Null),
11308 }
11309 }
11310 Ok(at.clone())
11311}
11312
11313fn unclosed_bracket(path: &str) -> async_graphql::Error {
11315 let message = format!("\"{}\" is not a JSON path: a bracket is never closed", path);
11316 validation_error(&message)
11317}
11318
11319fn parse_json_path(path: &str) -> Result<Vec<PathStep>, async_graphql::Error> {
11326 let mut steps = Vec::new();
11327 let mut chars = path.chars().peekable();
11328 if chars.peek() == Some(&'$') {
11330 chars.next();
11331 }
11332 while let Some(&c) = chars.peek() {
11333 match c {
11334 '.' => {
11336 chars.next();
11337 }
11338 '[' => {
11339 chars.next();
11340 let quote = match chars.peek() {
11341 Some(&q @ ('\'' | '"')) => {
11342 chars.next();
11343 Some(q)
11344 }
11345 _ => None,
11346 };
11347 let mut inner = String::new();
11348 loop {
11349 let Some(c) = chars.next() else {
11350 return Err(unclosed_bracket(path));
11351 };
11352 match (quote, c) {
11353 (Some(quote), c) if c == quote => break,
11354 (None, ']') => break,
11355 _ => inner.push(c),
11356 }
11357 }
11358 if quote.is_some() {
11360 match chars.next() {
11361 Some(']') => {}
11362 _ => return Err(unclosed_bracket(path)),
11363 }
11364 }
11365 match (quote, inner.parse::<usize>()) {
11368 (None, Ok(index)) => steps.push(PathStep::Index(index)),
11369 _ => steps.push(PathStep::Key(inner)),
11370 }
11371 }
11372 _ => {
11373 let mut key = String::new();
11374 while let Some(&c) = chars.peek() {
11375 if matches!(c, '.' | '[') {
11376 break;
11377 }
11378 key.push(c);
11379 chars.next();
11380 }
11381 steps.push(PathStep::Key(key));
11382 }
11383 }
11384 }
11385 Ok(steps)
11386}
11387
11388#[cfg(test)]
11389mod json_path_tests {
11390 use super::*;
11391
11392 fn steps(path: &str) -> Vec<PathStep> {
11393 parse_json_path(path).expect("the path parses")
11394 }
11395
11396 fn key(name: &str) -> PathStep {
11397 PathStep::Key(name.to_string())
11398 }
11399
11400 #[test]
11401 fn the_document_itself_has_no_steps() {
11402 assert_eq!(steps("$"), vec![]);
11403 assert_eq!(steps(""), vec![]);
11404 }
11405
11406 #[test]
11407 fn a_key_is_written_bare_or_after_a_dot() {
11408 assert_eq!(steps("a"), vec![key("a")]);
11409 assert_eq!(steps(".obj.c1"), vec![key("obj"), key("c1")]);
11410 assert_eq!(steps("._underscore"), vec![key("_underscore")]);
11411 }
11412
11413 #[test]
11414 fn a_bracket_holds_an_index_or_a_quoted_key() {
11415 assert_eq!(steps("arr[0]"), vec![key("arr"), PathStep::Index(0)]);
11416 assert_eq!(steps("['!@#$%^']"), vec![key("!@#$%^")]);
11417 assert_eq!(
11418 steps("translations['hello world!']"),
11419 vec![key("translations"), key("hello world!")]
11420 );
11421 }
11422
11423 #[test]
11425 fn a_dot_before_a_bracket_changes_nothing() {
11426 let expected = vec![key("objs"), PathStep::Index(0), key("\u{4f60}\u{597d}")];
11427 for spelling in [
11428 "objs[0]['\u{4f60}\u{597d}']",
11429 "objs[0][\"\u{4f60}\u{597d}\"]",
11430 "objs[0].[\"\u{4f60}\u{597d}\"]",
11431 "objs.[0].[\"\u{4f60}\u{597d}\"]",
11432 ] {
11433 assert_eq!(steps(spelling), expected, "{}", spelling);
11434 }
11435 }
11436
11437 #[test]
11438 fn a_bracket_that_never_closes_is_refused() {
11439 assert!(parse_json_path("arr[0").is_err());
11440 assert!(parse_json_path("['key'").is_err());
11441 }
11442
11443 #[test]
11444 fn a_step_the_document_does_not_have_reads_null() {
11445 let document = Value::from_json(serde_json::json!({
11446 "obj": {"c1": "c2"},
11447 "arr": [1, 2, 3],
11448 }))
11449 .expect("a document");
11450 let at = |path| walk_json_path(&document, path).expect("the path parses");
11451 assert_eq!(at(".obj.c1"), Value::String("c2".to_string()));
11452 assert_eq!(at("arr[1]"), Value::Number(2.into()));
11453 assert_eq!(at("arr[9]"), Value::Null);
11454 assert_eq!(at(".obj.nothing"), Value::Null);
11455 assert_eq!(at("obj[0]"), Value::Null);
11458 assert_eq!(at("arr.first"), Value::Null);
11459 }
11460}