1use crate::context::GraphQLContext;
7use crate::error::GraphQLError;
8use crate::schema::object::TableObjectType;
9use crate::schema::relationship::RelationshipField;
10use crate::schema::{build_schema, FederationEntity, GeneratedSchema, MutationType, SchemaConfig};
11use crate::subscription::{
12 generate_subscription_fields, NotifyBroker, SubscriptionField as SubField,
13};
14use async_graphql::dynamic::*;
15use async_graphql::extensions::{
16 Extension, ExtensionContext, ExtensionFactory, NextResolve, ResolveInfo,
17};
18use async_graphql::parser::types::{FragmentDefinition, Selection, SelectionSet, TypeCondition};
19use async_graphql::{Name, Positioned, SelectionField, ServerResult, Value};
20use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
21use axum::extract::State;
22use axum::response::IntoResponse;
23use postrust_core::schema_cache::SchemaCache;
24use sqlx::PgPool;
25use std::collections::{BTreeMap, HashMap, HashSet};
26use std::sync::Arc;
27use tokio::sync::RwLock;
28use tracing::{debug, info, trace};
29
30pub struct GraphQLState {
32 pub pool: PgPool,
34 pub schema_cache: Arc<SchemaCache>,
36 pub generated_schema: GeneratedSchema,
38 pub schema: Schema,
43 pub role_schemas: HashMap<(String, bool), Schema>,
59 pub ungranted_schema: Option<Schema>,
72 pub config: SchemaConfig,
74 pub subscription_fields: Vec<SubField>,
76 pub broker: Arc<RwLock<Option<NotifyBroker>>>,
78}
79
80struct NullableFederationEntity;
86
87impl ExtensionFactory for NullableFederationEntity {
88 fn create(&self) -> Arc<dyn Extension> {
89 Arc::new(Self)
90 }
91}
92
93#[async_graphql::async_trait::async_trait]
94impl Extension for NullableFederationEntity {
95 async fn resolve(
96 &self,
97 ctx: &ExtensionContext<'_>,
98 info: ResolveInfo<'_>,
99 next: NextResolve<'_>,
100 ) -> ServerResult<Option<Value>> {
101 let parent_type = info.parent_type;
110 let return_type = info.return_type;
111 match next.run(ctx, info).await {
112 Err(error)
113 if error.message
114 == "internal: invalid value for union \"_Entity\", expected \"FieldValue::WithType\""
115 && parent_type == "[_Entity]"
116 && return_type == "_Entity" =>
117 {
118 Ok(Some(Value::Null))
119 }
120 result => result,
121 }
122 }
123}
124
125fn build_role_schemas(
136 schema_cache: &Arc<SchemaCache>,
137 config: &SchemaConfig,
138) -> Result<HashMap<(String, bool), Schema>, GraphQLError> {
139 if !config.names.has_permissions() {
140 return Ok(HashMap::new());
141 }
142
143 let mut schemas = HashMap::new();
144 for role in config.names.roles() {
145 if role == postrust_auth::hasura::ADMIN_ROLE {
149 continue;
150 }
151
152 let variants: &[bool] = match crate::role::has_backend_only(&config.names, role) {
156 true => &[false, true],
157 false => &[false],
158 };
159
160 for &backend in variants {
161 let view = Arc::new(crate::role::cache_for_role(
162 schema_cache,
163 &config.names,
164 role,
165 backend,
166 ));
167 let mut role_config = config.clone();
168 role_config.role = Some(role.to_string());
169
170 let generated = build_schema(&view, &role_config);
171 let built = build_dynamic_schema(
176 &generated,
177 &view,
178 None,
179 role_config.max_rows,
180 Arc::new(role_config.names.clone()),
181 role_config.subscription_refresh(),
182 Some(role),
183 );
184
185 match built {
186 Ok(schema) => {
187 schemas.insert((role.to_string(), backend), schema);
188 }
189 Err(e) => tracing::error!(
196 "GraphQL schema for role \"{}\" could not be built, \
197 so that role is refused: {}",
198 role,
199 e
200 ),
201 }
202 }
203 }
204
205 tracing::info!("GraphQL schemas built for {} roles", schemas.len());
206 Ok(schemas)
207}
208
209fn build_ungranted_schema(
216 schema_cache: &Arc<SchemaCache>,
217 config: &SchemaConfig,
218) -> Option<Schema> {
219 if !config.names.has_permissions() {
220 return None;
221 }
222 const NOBODY: &str = "\u{0}ungranted";
225 let view = Arc::new(crate::role::cache_for_role(
226 schema_cache,
227 &config.names,
228 NOBODY,
229 false,
230 ));
231 let mut role_config = config.clone();
232 role_config.role = Some(NOBODY.to_string());
233 let generated = build_schema(&view, &role_config);
234 build_dynamic_schema(
235 &generated,
236 &view,
237 None,
238 role_config.max_rows,
239 Arc::new(role_config.names.clone()),
240 role_config.subscription_refresh(),
241 Some(NOBODY),
242 )
243 .map_err(|e| {
244 tracing::error!(
245 "the schema for a role with nothing granted could not be built, so such a role is refused instead: {}",
246 e
247 )
248 })
249 .ok()
250}
251
252impl GraphQLState {
253 pub fn new(
255 pool: PgPool,
256 schema_cache: Arc<SchemaCache>,
257 config: SchemaConfig,
258 ) -> Result<Self, GraphQLError> {
259 let generated_schema = build_schema(&schema_cache, &config);
260 let subscription_fields = if config.enable_subscriptions {
261 generate_subscription_fields(&schema_cache, &generated_schema)
262 } else {
263 Vec::new()
264 };
265 let schema = build_dynamic_schema(
266 &generated_schema,
267 &schema_cache,
268 if config.enable_subscriptions {
269 Some(subscription_fields.as_slice())
270 } else {
271 None
272 },
273 config.max_rows,
274 Arc::new(config.names.clone()),
275 config.subscription_refresh(),
276 None,
277 )?;
278
279 let role_schemas = build_role_schemas(&schema_cache, &config)?;
280 let ungranted_schema = build_ungranted_schema(&schema_cache, &config);
281
282 Ok(Self {
283 pool: pool.clone(),
284 schema_cache,
285 generated_schema,
286 schema,
287 role_schemas,
288 ungranted_schema,
289 config,
290 subscription_fields,
291 broker: Arc::new(RwLock::new(None)),
292 })
293 }
294
295 pub fn schema_for(&self, role: Option<&str>, backend: bool) -> Option<&Schema> {
308 if !self.config.names.has_permissions() {
309 return Some(&self.schema);
310 }
311 match role {
312 None | Some(postrust_auth::hasura::ADMIN_ROLE) => Some(&self.schema),
313 Some(role) => self
314 .role_schemas
315 .get(&(role.to_string(), backend))
316 .or_else(|| self.role_schemas.get(&(role.to_string(), false)))
319 .or(self.ungranted_schema.as_ref()),
320 }
321 }
322
323 #[allow(clippy::needless_update)]
325 pub fn rebuild(&mut self) -> Result<(), GraphQLError> {
326 self.role_schemas = build_role_schemas(&self.schema_cache, &self.config)?;
327 self.ungranted_schema = build_ungranted_schema(&self.schema_cache, &self.config);
328 self.generated_schema = build_schema(&self.schema_cache, &self.config);
329 self.subscription_fields = if self.config.enable_subscriptions {
330 generate_subscription_fields(&self.schema_cache, &self.generated_schema)
331 } else {
332 Vec::new()
333 };
334 self.schema = build_dynamic_schema(
335 &self.generated_schema,
336 &self.schema_cache,
337 if self.config.enable_subscriptions {
338 Some(self.subscription_fields.as_slice())
339 } else {
340 None
341 },
342 self.config.max_rows,
343 Arc::new(self.config.names.clone()),
344 self.config.subscription_refresh(),
345 None,
346 )?;
347 Ok(())
348 }
349
350 pub async fn init_subscriptions(&self) -> Result<(), crate::subscription::BrokerError> {
354 if !self.config.enable_subscriptions {
355 return Ok(());
356 }
357
358 let broker = NotifyBroker::new(self.pool.clone());
359
360 let channels: Vec<String> = self
362 .subscription_fields
363 .iter()
364 .map(|f| f.channel_name())
365 .collect();
366
367 if !channels.is_empty() {
368 broker.start(channels).await?;
369 info!(
370 "Subscription broker started with {} channels",
371 self.subscription_fields.len()
372 );
373 }
374
375 let mut broker_guard = self.broker.write().await;
377 *broker_guard = Some(broker);
378
379 Ok(())
380 }
381
382 pub async fn stop_subscriptions(&self) {
384 let broker_guard = self.broker.read().await;
385 if let Some(broker) = broker_guard.as_ref() {
386 broker.stop().await;
387 }
388 }
389
390 pub async fn 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 }
2759
2760 futures::stream::unfold(
2761 Live {
2762 ctx,
2763 source,
2764 wake,
2765 sent: None,
2766 started: false,
2767 failed: false,
2768 },
2769 |mut live: Live<'a>| async move {
2770 if live.failed {
2771 return None;
2772 }
2773 loop {
2774 if live.started {
2775 live.wake.next().await?;
2776 } else {
2777 live.started = true;
2778 }
2779 let read = read_live(&live.ctx, &live.source).await;
2780 match read {
2781 Err(e) => {
2789 live.failed = true;
2790 return Some((Err(e), live));
2791 }
2792 Ok((seen, value)) => {
2793 if live.sent.as_ref() == Some(&seen) {
2794 continue;
2795 }
2796 live.sent = Some(seen);
2797 let value = value.unwrap_or_else(|| FieldValue::value(Value::Null));
2798 return Some((Ok(value), live));
2799 }
2800 }
2801 }
2802 },
2803 )
2804}
2805
2806struct QueryFieldSpec {
2808 schema_name: String,
2809 table_name: String,
2810 type_name: String,
2811 is_by_pk: bool,
2812 pk_columns: Vec<(String, String)>,
2813 max_rows: Option<i64>,
2814 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2815 names: Arc<crate::names::NameOverrides>,
2816 call: Option<Arc<FunctionCall>>,
2819}
2820
2821#[derive(Clone)]
2823struct EntityLookup {
2824 schema_name: String,
2825 table_name: String,
2826 type_name: String,
2827 key_columns: Vec<EntityKeyColumn>,
2828 row_column_types: HashMap<String, String>,
2829}
2830
2831#[derive(Clone)]
2832struct EntityKeyColumn {
2833 field_name: String,
2834 column_name: String,
2835 pg_type: String,
2836 explicit_id: bool,
2837}
2838
2839struct EntityResolverState {
2841 entities: HashMap<String, EntityLookup>,
2842 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2843 names: Arc<crate::names::NameOverrides>,
2844 max_rows: Option<i64>,
2845}
2846
2847impl EntityResolverState {
2848 fn new(
2849 generated: &GeneratedSchema,
2850 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
2851 names: Arc<crate::names::NameOverrides>,
2852 max_rows: Option<i64>,
2853 ) -> Self {
2854 let mut entities = HashMap::new();
2855 for (type_name, entity) in &generated.federation_entities {
2856 let Some(object) = generated.object_types.get(type_name) else {
2857 continue;
2858 };
2859 let mut key_columns = Vec::with_capacity(entity.key_fields.len());
2860 for field_name in &entity.key_fields {
2861 let column_name = names
2862 .column_source(&object.table.schema, &object.table.name, field_name)
2863 .unwrap_or(field_name);
2864 let Some(column) = object.table.get_column(column_name) else {
2865 continue;
2866 };
2867 key_columns.push(EntityKeyColumn {
2868 field_name: field_name.clone(),
2869 column_name: column.name.clone(),
2870 pg_type: column.nominal_type.clone(),
2871 explicit_id: matches!(
2872 names.column_type(&object.table.schema, &object.table.name, &column.name),
2873 Some(crate::types::GraphQLType::Id)
2874 ),
2875 });
2876 }
2877 if key_columns.len() != entity.key_fields.len() {
2878 continue;
2879 }
2880 entities.insert(
2881 type_name.clone(),
2882 EntityLookup {
2883 schema_name: object.table.schema.clone(),
2884 table_name: object.table.name.clone(),
2885 type_name: type_name.clone(),
2886 key_columns,
2887 row_column_types: exposed_column_types(&object.table, names.as_ref()),
2888 },
2889 );
2890 }
2891 Self {
2892 entities,
2893 relationships,
2894 names,
2895 max_rows,
2896 }
2897 }
2898}
2899
2900struct EntityRepresentation {
2901 index: usize,
2902 type_name: String,
2903 values: serde_json::Map<String, serde_json::Value>,
2904}
2905
2906fn entity_field_applicability(
2913 selection_set: &SelectionSet,
2914 fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2915 type_name: &str,
2916) -> Vec<bool> {
2917 fn condition_matches(condition: Option<&Positioned<TypeCondition>>, type_name: &str) -> bool {
2918 condition.is_none_or(|condition| {
2919 let condition = condition.node.on.node.as_str();
2920 condition == type_name || condition == "_Entity"
2921 })
2922 }
2923
2924 fn collect(
2925 selection_set: &SelectionSet,
2926 fragments: &HashMap<Name, Positioned<FragmentDefinition>>,
2927 type_name: &str,
2928 parent_matches: bool,
2929 applicability: &mut Vec<bool>,
2930 ) {
2931 for selection in &selection_set.items {
2932 match &selection.node {
2933 Selection::Field(_) => applicability.push(parent_matches),
2934 Selection::FragmentSpread(spread) => {
2935 let Some(fragment) = fragments.get(&spread.node.fragment_name.node) else {
2936 continue;
2937 };
2938 collect(
2939 &fragment.node.selection_set.node,
2940 fragments,
2941 type_name,
2942 parent_matches
2943 && condition_matches(Some(&fragment.node.type_condition), type_name),
2944 applicability,
2945 );
2946 }
2947 Selection::InlineFragment(fragment) => collect(
2948 &fragment.node.selection_set.node,
2949 fragments,
2950 type_name,
2951 parent_matches
2952 && condition_matches(fragment.node.type_condition.as_ref(), type_name),
2953 applicability,
2954 ),
2955 }
2956 }
2957 }
2958
2959 let mut applicability = Vec::new();
2960 collect(
2961 selection_set,
2962 fragments,
2963 type_name,
2964 true,
2965 &mut applicability,
2966 );
2967 applicability
2968}
2969
2970fn entity_selection_fields<'ctx, 'a>(
2971 ctx: &'ctx ResolverContext<'a>,
2972 type_name: &str,
2973) -> Vec<SelectionField<'ctx>> {
2974 let applicability = entity_field_applicability(
2975 &ctx.ctx.item.node.selection_set.node,
2976 &ctx.ctx.query_env.fragments,
2977 type_name,
2978 );
2979 let fields: Vec<_> = ctx.field().selection_set().collect();
2980 debug_assert_eq!(fields.len(), applicability.len());
2981 fields
2982 .into_iter()
2983 .zip(applicability)
2984 .filter_map(|(field, applies)| applies.then_some(field))
2985 .collect()
2986}
2987
2988async fn resolve_entities<'a>(
2989 ctx: &ResolverContext<'a>,
2990 state: &EntityResolverState,
2991) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
2992 let representations = ctx.args.try_get("representations")?.list()?;
2993 let mut grouped: HashMap<String, Vec<EntityRepresentation>> = HashMap::new();
2994 let mut count = 0;
2995 for (index, representation) in representations.iter().enumerate() {
2996 count += 1;
2997 let value = accessor_to_json(&representation);
2998 let serde_json::Value::Object(values) = value else {
2999 return Err(async_graphql::Error::new(
3000 "entity representation must be an object",
3001 ));
3002 };
3003 let type_name = values
3004 .get("__typename")
3005 .and_then(serde_json::Value::as_str)
3006 .ok_or_else(|| {
3007 async_graphql::Error::new("entity representation is missing __typename")
3008 })?
3009 .to_string();
3010 grouped
3011 .entry(type_name.clone())
3012 .or_default()
3013 .push(EntityRepresentation {
3014 index,
3015 type_name,
3016 values,
3017 });
3018 }
3019
3020 let mut resolved: Vec<Option<FieldValue<'a>>> =
3021 std::iter::repeat_with(|| None).take(count).collect();
3022 for (type_name, representations) in grouped {
3023 let lookup = state.entities.get(&type_name).ok_or_else(|| {
3024 async_graphql::Error::new(format!("unknown federation entity \"{}\"", type_name))
3025 })?;
3026 let rows = resolve_entity_group(ctx, lookup, &representations, state).await?;
3027 for (ordinality, representation) in representations.into_iter().enumerate() {
3028 resolved[representation.index] = Some(match &rows[ordinality] {
3029 Some(row) => FieldValue::value(json_to_value(row.clone()))
3030 .with_type(representation.type_name),
3031 None => FieldValue::NULL,
3032 });
3033 }
3034 }
3035
3036 Ok(Some(FieldValue::list(resolved.into_iter().map(|value| {
3037 value.expect("every representation was resolved or errored")
3038 }))))
3039}
3040
3041async fn resolve_entity_group(
3042 ctx: &ResolverContext<'_>,
3043 lookup: &EntityLookup,
3044 representations: &[EntityRepresentation],
3045 state: &EntityResolverState,
3046) -> Result<Vec<Option<serde_json::Value>>, async_graphql::Error> {
3047 if representations.is_empty() {
3048 return Ok(Vec::new());
3049 }
3050
3051 let pool = ctx.data::<PgPool>()?;
3052 let gql_ctx = ctx.data::<GraphQLContext>()?;
3053 let mut bound_values = Vec::new();
3054 let mut value_rows = Vec::with_capacity(representations.len());
3055 for (ordinality, representation) in representations.iter().enumerate() {
3056 let normalized_values = normalized_entity_key_values(
3057 &representation.values,
3058 &lookup.key_columns,
3059 &lookup.type_name,
3060 )?;
3061 let mut values = Vec::with_capacity(lookup.key_columns.len() + 1);
3062 for (key_column, value) in lookup.key_columns.iter().zip(normalized_values) {
3063 values.push(format!(
3064 "${}::{}",
3065 bound_values.len() + 1,
3066 key_column.pg_type
3067 ));
3068 bound_values.push(value);
3069 }
3070 values.push(ordinality.to_string());
3071 value_rows.push(format!("({})", values.join(", ")));
3072 }
3073
3074 let representation_alias = "pgrst_entity_representation";
3075 let ordinality_column = "pgrst_entity_ordinality";
3076 let key_aliases: Vec<String> = (0..lookup.key_columns.len())
3077 .map(|index| format!("pgrst_entity_key_{index}"))
3078 .collect();
3079 let mut representation_columns: Vec<String> = key_aliases
3080 .iter()
3081 .map(|column| postrust_sql::escape_ident(column))
3082 .collect();
3083 representation_columns.push(postrust_sql::escape_ident(ordinality_column));
3084 let representations_sql = format!(
3085 "(VALUES {}) AS {} ({})",
3086 value_rows.join(", "),
3087 postrust_sql::escape_ident(representation_alias),
3088 representation_columns.join(", ")
3089 );
3090 let key_predicate = lookup
3091 .key_columns
3092 .iter()
3093 .zip(&key_aliases)
3094 .map(|(key_column, key_alias)| {
3095 format!(
3096 "{}.{} = {}.{}",
3097 postrust_sql::escape_ident(READ_ROW),
3098 postrust_sql::escape_ident(&key_column.column_name),
3099 postrust_sql::escape_ident(representation_alias),
3100 postrust_sql::escape_ident(key_alias)
3101 )
3102 })
3103 .collect::<Vec<_>>()
3104 .join(" AND ");
3105 let mut where_sql = format!(" WHERE ({key_predicate})");
3106
3107 let permission = permission_predicate(
3108 &gql_ctx.caller(),
3109 state.names.as_ref(),
3110 &lookup.schema_name,
3111 &lookup.table_name,
3112 )?;
3113 if let Some(predicate) = permission {
3114 let guard = gql_ctx
3115 .schema_cache
3116 .get()
3117 .await
3118 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3119 let cache = guard
3120 .as_ref()
3121 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3122 let scope = WhereScope::table(
3123 &lookup.schema_name,
3124 &lookup.table_name,
3125 &lookup.type_name,
3126 state.names.as_ref(),
3127 )
3128 .under_alias(READ_ROW)
3129 .with_resolution(cache, state.relationships.as_ref())
3130 .for_caller(gql_ctx.caller());
3131 let (filter_sql, filter_values) =
3132 build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3133 if !filter_sql.is_empty() {
3134 where_sql = format!(
3135 "{} AND ({})",
3136 where_sql,
3137 filter_sql.trim_start_matches("WHERE ")
3138 );
3139 bound_values.extend(filter_values);
3140 }
3141 }
3142
3143 let source = format!(
3144 "{}.{} AS {}",
3145 postrust_sql::escape_ident(&lookup.schema_name),
3146 postrust_sql::escape_ident(&lookup.table_name),
3147 postrust_sql::escape_ident(READ_ROW)
3148 );
3149 let source_rows = format!(
3150 "SELECT {}.* FROM {}{}",
3151 postrust_sql::escape_ident(READ_ROW),
3152 source,
3153 where_sql
3154 );
3155 let mut projection = {
3156 let guard = gql_ctx
3157 .schema_cache
3158 .get()
3159 .await
3160 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3161 guard
3162 .as_ref()
3163 .and_then(|cache| {
3164 cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3165 &lookup.schema_name,
3166 &lookup.table_name,
3167 ))
3168 })
3169 .and_then(|table| rename_projection(table, "src", state.names.as_ref()))
3170 .unwrap_or_else(|| "src.*".to_string())
3171 };
3172 let projected_rows = {
3173 let guard = gql_ctx
3174 .schema_cache
3175 .get()
3176 .await
3177 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3178 let cache = guard
3179 .as_ref()
3180 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3181 let table = cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
3182 &lookup.schema_name,
3183 &lookup.table_name,
3184 ));
3185 let selected_fields = entity_selection_fields(ctx, &lookup.type_name);
3186 let mut param_idx = bound_values.len() + 1;
3187 let computed = match table {
3188 Some(table) => computed_projections(
3189 table,
3190 selected_fields.iter().copied(),
3191 "src",
3192 state.names.as_ref(),
3193 cache,
3194 &mut param_idx,
3195 &mut bound_values,
3196 )?,
3197 None => Vec::new(),
3198 };
3199 let embeds = build_embed_expressions(
3200 &gql_ctx.caller(),
3201 cache,
3202 state.relationships.as_ref(),
3203 &lookup.type_name,
3204 "src",
3205 selected_fields,
3206 state.max_rows,
3207 &mut 0,
3208 &mut param_idx,
3209 &mut bound_values,
3210 state.names.as_ref(),
3211 )?;
3212 for expression in &computed {
3213 projection.push_str(", ");
3214 projection.push_str(expression);
3215 }
3216 for (field_name, expression) in &embeds {
3217 projection.push_str(", ");
3218 projection.push_str(expression);
3219 projection.push_str(" AS ");
3220 projection.push_str(&postrust_sql::escape_ident(field_name));
3221 }
3222 format!("SELECT {} FROM ({}) AS src", projection, source_rows)
3223 };
3224 let sql = format!(
3229 "SELECT json_build_array({}.{}, {}) FROM {} CROSS JOIN LATERAL ({}) AS t",
3230 postrust_sql::escape_ident(representation_alias),
3231 postrust_sql::escape_ident(ordinality_column),
3232 row_json("t", &lookup.row_column_types),
3233 representations_sql,
3234 projected_rows,
3235 );
3236 let mut tx = begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3237 let rows = execute_query_on(&mut tx, &sql, &bound_values).await?;
3238 tx.commit().await?;
3239
3240 let mut by_ordinal = std::iter::repeat_with(|| None)
3241 .take(representations.len())
3242 .collect::<Vec<_>>();
3243 for result in rows {
3244 let serde_json::Value::Array(mut pair) = result else {
3245 return Err(async_graphql::Error::new(
3246 "entity query returned an invalid result",
3247 ));
3248 };
3249 if pair.len() != 2 {
3250 return Err(async_graphql::Error::new(
3251 "entity query returned an invalid result",
3252 ));
3253 }
3254 let row = pair.pop().expect("the result pair has two values");
3255 let ordinality = pair
3256 .pop()
3257 .and_then(|value| value.as_u64())
3258 .and_then(|value| usize::try_from(value).ok())
3259 .filter(|value| *value < by_ordinal.len())
3260 .ok_or_else(|| async_graphql::Error::new("entity query returned an invalid ordinal"))?;
3261 by_ordinal[ordinality] = Some(row);
3262 }
3263 Ok(by_ordinal)
3264}
3265
3266fn normalized_entity_key_values(
3270 values: &serde_json::Map<String, serde_json::Value>,
3271 key_columns: &[EntityKeyColumn],
3272 type_name: &str,
3273) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
3274 let mut key = Vec::with_capacity(key_columns.len());
3275 for key_column in key_columns {
3276 let value = values.get(&key_column.field_name).ok_or_else(|| {
3277 async_graphql::Error::new(format!(
3278 "entity representation for \"{}\" is missing key field \"{}\"",
3279 type_name, key_column.field_name
3280 ))
3281 })?;
3282 key.push(normalize_entity_key_value(type_name, key_column, value)?);
3283 }
3284 Ok(key)
3285}
3286
3287fn normalize_entity_key_value(
3288 type_name: &str,
3289 key_column: &EntityKeyColumn,
3290 value: &serde_json::Value,
3291) -> Result<serde_json::Value, async_graphql::Error> {
3292 let invalid = |expected: &str| {
3293 async_graphql::Error::new(format!(
3294 "entity representation for \"{}\" has invalid key field \"{}\": expected {}, found {}",
3295 type_name,
3296 key_column.field_name,
3297 expected,
3298 json_value_kind(value)
3299 ))
3300 };
3301
3302 if key_column.explicit_id {
3303 return normalize_graphql_id(value, &key_column.pg_type).ok_or_else(|| {
3304 invalid(&format!(
3305 "an ID compatible with PostgreSQL type \"{}\"",
3306 key_column.pg_type
3307 ))
3308 });
3309 }
3310
3311 let pg_type = key_column.pg_type.trim().to_ascii_lowercase();
3312 let normalized = if matches!(pg_type.as_str(), "int8" | "bigint") {
3313 match value {
3314 serde_json::Value::Number(number) => number.as_i64(),
3315 serde_json::Value::String(text) => text.parse::<i64>().ok(),
3316 _ => None,
3317 }
3318 .map(serde_json::Value::from)
3319 } else if integer_bounds(&pg_type).is_some() {
3320 value.as_i64().and_then(|integer| {
3321 let (min, max) = integer_bounds(&pg_type)?;
3322 (min..=max)
3323 .contains(&integer)
3324 .then(|| serde_json::Value::from(integer))
3325 })
3326 } else if matches!(
3327 pg_type.as_str(),
3328 "float4" | "float8" | "real" | "double precision"
3329 ) {
3330 value
3331 .as_f64()
3332 .and_then(serde_json::Number::from_f64)
3333 .map(serde_json::Value::Number)
3334 } else if matches!(pg_type.as_str(), "numeric" | "decimal") {
3335 match value {
3336 serde_json::Value::Number(number) => {
3337 Some(serde_json::Value::String(number.to_string()))
3338 }
3339 serde_json::Value::String(text) if text.parse::<sqlx::types::BigDecimal>().is_ok() => {
3340 Some(serde_json::Value::String(text.clone()))
3341 }
3342 _ => None,
3343 }
3344 } else if matches!(pg_type.as_str(), "bool" | "boolean") {
3345 value.as_bool().map(serde_json::Value::Bool)
3346 } else if matches!(pg_type.as_str(), "json" | "jsonb") {
3347 Some(value.clone())
3348 } else if is_array_type(&pg_type) {
3349 value.is_array().then(|| value.clone())
3350 } else {
3351 value
3352 .as_str()
3353 .map(|text| serde_json::Value::String(text.to_string()))
3354 };
3355
3356 normalized.ok_or_else(|| {
3357 invalid(&format!(
3358 "a value compatible with PostgreSQL type \"{}\"",
3359 key_column.pg_type
3360 ))
3361 })
3362}
3363
3364fn normalize_graphql_id(value: &serde_json::Value, pg_type: &str) -> Option<serde_json::Value> {
3365 if integer_bounds(pg_type).is_some() {
3366 let integer = match value {
3367 serde_json::Value::Number(number) => number.as_i64(),
3368 serde_json::Value::String(text) => text.parse::<i64>().ok(),
3369 _ => None,
3370 }?;
3371 let (min, max) = integer_bounds(pg_type)?;
3372 return (min..=max)
3373 .contains(&integer)
3374 .then(|| serde_json::Value::from(integer));
3375 }
3376 if pg_type.eq_ignore_ascii_case("uuid") {
3377 return normalize_uuid(value).map(serde_json::Value::String);
3378 }
3379 if is_text_type(pg_type) {
3380 return match value {
3381 serde_json::Value::String(s) => Some(serde_json::Value::String(s.clone())),
3382 serde_json::Value::Number(n) if n.is_i64() || n.is_u64() => {
3383 Some(serde_json::Value::String(n.to_string()))
3384 }
3385 _ => None,
3386 };
3387 }
3388 None
3389}
3390
3391fn integer_bounds(pg_type: &str) -> Option<(i64, i64)> {
3392 match pg_type.trim().to_ascii_lowercase().as_str() {
3393 "int2" | "smallint" => Some((i16::MIN.into(), i16::MAX.into())),
3394 "int4" | "int" | "integer" => Some((i32::MIN.into(), i32::MAX.into())),
3395 "int8" | "bigint" => Some((i64::MIN, i64::MAX)),
3396 _ => None,
3397 }
3398}
3399
3400fn is_text_type(pg_type: &str) -> bool {
3401 matches!(
3402 pg_type.trim().to_ascii_lowercase().as_str(),
3403 "text" | "varchar" | "character varying" | "char" | "character" | "bpchar" | "citext"
3404 )
3405}
3406
3407fn normalize_uuid(value: &serde_json::Value) -> Option<String> {
3408 sqlx::types::Uuid::parse_str(value.as_str()?)
3409 .ok()
3410 .map(|uuid| uuid.to_string())
3411}
3412
3413fn json_value_kind(value: &serde_json::Value) -> &'static str {
3414 match value {
3415 serde_json::Value::Null => "null",
3416 serde_json::Value::Bool(_) => "a boolean",
3417 serde_json::Value::Number(_) => "a number",
3418 serde_json::Value::String(_) => "a string",
3419 serde_json::Value::Array(_) => "an array",
3420 serde_json::Value::Object(_) => "an object",
3421 }
3422}
3423
3424fn add_function_fields(
3431 mut root: Object,
3432 generated: &GeneratedSchema,
3433 volatile: bool,
3434 max_rows: Option<i64>,
3435 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3436 names: Arc<crate::names::NameOverrides>,
3437) -> Object {
3438 let taken: HashSet<&str> = if volatile {
3443 generated
3444 .mutation_fields
3445 .iter()
3446 .map(|f| f.name.as_str())
3447 .collect()
3448 } else {
3449 generated
3450 .query_fields
3451 .iter()
3452 .map(|f| f.name.as_str())
3453 .collect()
3454 };
3455
3456 for function in &generated.function_fields {
3457 if function.volatile != volatile {
3458 continue;
3459 }
3460 if taken.contains(function.name.as_str()) {
3461 tracing::warn!(
3462 "not exposing {}.{}({}) -- a table already has a root field \
3463 called {}",
3464 function.schema_name,
3465 function.function_name,
3466 function.argument_signature(),
3467 function.name,
3468 );
3469 continue;
3470 }
3471 let spec = Arc::new(QueryFieldSpec {
3472 schema_name: function.returns_table.0.clone(),
3473 table_name: function.returns_table.1.clone(),
3474 type_name: function.returns.clone(),
3475 is_by_pk: false,
3476 pk_columns: Vec::new(),
3477 max_rows,
3478 relationships: Arc::clone(&relationships),
3479 names: Arc::clone(&names),
3480 call: Some(Arc::new(FunctionCall {
3481 schema: function.schema_name.clone(),
3482 name: function.function_name.clone(),
3483 arguments: function.arguments.clone(),
3484 session_argument: function.session_argument.clone(),
3485 })),
3486 });
3487
3488 let mut field = Field::new(
3489 &function.name,
3490 TypeRef::named_nn_list_nn(&function.returns),
3491 move |ctx| {
3492 let spec = Arc::clone(&spec);
3493 FieldFuture::new(async move { resolve_query(&ctx, &spec).await })
3494 },
3495 );
3496 if !function.arguments.is_empty() {
3497 field = field.argument(InputValue::new(
3498 "args",
3499 TypeRef::named_nn(format!("{}_args", function.name)),
3500 ));
3501 }
3502 field = field
3503 .argument(InputValue::new(
3504 "where",
3505 TypeRef::named(crate::input::bool_exp::bool_exp_type_name(
3506 &function.local_returns,
3507 )),
3508 ))
3509 .argument(InputValue::new(
3510 "order_by",
3511 TypeRef::named_nn_list(crate::input::order_by::order_by_type_name(
3512 &function.local_returns,
3513 )),
3514 ))
3515 .argument(InputValue::new(
3516 "distinct_on",
3517 TypeRef::named_nn_list(crate::input::order_by::select_column_type_name(
3518 &function.local_returns,
3519 )),
3520 ))
3521 .argument(InputValue::new("limit", TypeRef::named("Int")))
3522 .argument(InputValue::new("offset", TypeRef::named("Int")));
3523 if let Some(description) = &function.description {
3524 field = field.description(description);
3525 }
3526 root = root.field(field);
3527
3528 let agg_spec = Arc::new(AggregateSpec {
3533 schema_name: function.returns_table.0.clone(),
3534 table_name: function.returns_table.1.clone(),
3535 type_name: function.returns.clone(),
3536 max_rows,
3537 relationships: Arc::clone(&relationships),
3538 names: Arc::clone(&names),
3539 call: Some(Arc::new(FunctionCall {
3540 schema: function.schema_name.clone(),
3541 name: function.function_name.clone(),
3542 arguments: function.arguments.clone(),
3543 session_argument: function.session_argument.clone(),
3544 })),
3545 });
3546 let mut aggregate = Field::new(
3547 format!("{}_aggregate", function.name),
3548 TypeRef::named_nn(crate::schema::aggregate::aggregate_type_name(
3549 &function.local_returns,
3550 )),
3551 move |ctx| {
3552 let agg_spec = Arc::clone(&agg_spec);
3553 FieldFuture::new(async move { resolve_aggregate(&ctx, &agg_spec).await })
3554 },
3555 );
3556 if !function.arguments.is_empty() {
3557 aggregate = aggregate.argument(InputValue::new(
3558 "args",
3559 TypeRef::named_nn(format!("{}_args", function.name)),
3560 ));
3561 }
3562 aggregate = with_row_arguments(aggregate, &function.local_returns).description(format!(
3563 "fetch aggregated fields from the table: \"{}\"",
3564 function.returns_table.1
3565 ));
3566 root = root.field(aggregate);
3567 }
3568 root
3569}
3570
3571fn function_source(
3579 ctx: &ResolverContext<'_>,
3580 call: &FunctionCall,
3581 alias: &str,
3582 bound_values: &mut Vec<serde_json::Value>,
3583) -> Result<String, async_graphql::Error> {
3584 let given = ctx
3585 .args
3586 .try_get("args")
3587 .ok()
3588 .map(|v| accessor_to_json(&v))
3589 .unwrap_or_else(|| serde_json::json!({}));
3590 let mut passed = Vec::new();
3591 for (name, _, required) in &call.arguments {
3592 match given.get(name) {
3593 None if *required => {
3594 return Err(async_graphql::Error::new(format!(
3595 "{} needs the argument \"{}\"",
3596 call.name, name
3597 )))
3598 }
3599 None => continue,
3602 Some(value) => {
3603 let (_, pg_type, _) = call
3610 .arguments
3611 .iter()
3612 .find(|(argument, _, _)| argument == name)
3613 .expect("iterating the arguments");
3614 passed.push(format!(
3615 "{} => ${}::{}",
3616 postrust_sql::escape_ident(name),
3617 bound_values.len() + 1,
3618 pg_type
3619 ));
3620 bound_values.push(value.clone());
3621 }
3622 }
3623 }
3624 if let Some(session) = &call.session_argument {
3628 passed.push(format!(
3629 "{} => {}",
3630 postrust_sql::escape_ident(session),
3631 SESSION_ARGUMENT
3632 ));
3633 }
3634 Ok(format!(
3635 "{}.{}({}) AS {}",
3636 postrust_sql::escape_ident(&call.schema),
3637 postrust_sql::escape_ident(&call.name),
3638 passed.join(", "),
3639 postrust_sql::escape_ident(alias)
3640 ))
3641}
3642
3643struct FunctionCall {
3645 schema: String,
3646 name: String,
3647 arguments: Vec<(String, String, bool)>,
3648 session_argument: Option<String>,
3651}
3652
3653struct AggregateSpec {
3655 schema_name: String,
3656 table_name: String,
3657 type_name: String,
3658 max_rows: Option<i64>,
3659 relationships: Arc<HashMap<String, Vec<RelationshipField>>>,
3660 names: Arc<crate::names::NameOverrides>,
3661 call: Option<Arc<FunctionCall>>,
3665}
3666
3667async fn resolve_aggregate<'a>(
3674 ctx: &ResolverContext<'a>,
3675 spec: &AggregateSpec,
3676) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
3677 Ok(Some(FieldValue::value(aggregate_value(ctx, spec).await?)))
3678}
3679
3680async fn aggregate_value(
3685 ctx: &ResolverContext<'_>,
3686 spec: &AggregateSpec,
3687) -> Result<Value, async_graphql::Error> {
3688 use crate::schema::aggregate as agg;
3689
3690 let pool = ctx.data::<PgPool>()?;
3691 let gql_ctx = ctx.data::<GraphQLContext>()?;
3692
3693 let mut bound_values: Vec<serde_json::Value> = Vec::new();
3694 let source = match &spec.call {
3698 None => format!(
3699 "{}.{}",
3700 postrust_sql::escape_ident(&spec.schema_name),
3701 postrust_sql::escape_ident(&spec.table_name)
3702 ),
3703 Some(call) => function_source(ctx, call, &spec.table_name, &mut bound_values)?,
3704 };
3705
3706 let mut where_sql = String::new();
3707 {
3708 let requested = ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v));
3712 let permission = permission_predicate(
3713 &gql_ctx.caller(),
3714 spec.names.as_ref(),
3715 &spec.schema_name,
3716 &spec.table_name,
3717 )?;
3718 if let Some(predicate) = and_predicates(requested, permission) {
3719 let guard = gql_ctx
3720 .schema_cache
3721 .get()
3722 .await
3723 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3724 let cache = guard
3725 .as_ref()
3726 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3727 let scope = match &spec.call {
3734 None => WhereScope::table(
3735 &spec.schema_name,
3736 &spec.table_name,
3737 &spec.type_name,
3738 spec.names.as_ref(),
3739 )
3740 .with_resolution(cache, spec.relationships.as_ref()),
3741 Some(_) => WhereScope::for_alias(
3742 &spec.schema_name,
3743 &spec.table_name,
3744 &spec.table_name,
3745 &spec.type_name,
3746 cache,
3747 spec.relationships.as_ref(),
3748 spec.names.as_ref(),
3749 ),
3750 }
3751 .for_caller(gql_ctx.caller());
3752 let (sql, values) =
3753 build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
3754 if !sql.is_empty() {
3755 where_sql = format!(" {}", sql);
3756 bound_values.extend(values);
3757 }
3758 }
3759 }
3760
3761 let order_sql = build_order_by_clause(
3762 ctx,
3763 &gql_ctx.schema_cache,
3764 &spec.schema_name,
3765 &spec.table_name,
3766 &spec.type_name,
3767 spec.relationships.as_ref(),
3768 &format!(
3769 "{}.{}",
3770 postrust_sql::escape_ident(&spec.schema_name),
3771 postrust_sql::escape_ident(&spec.table_name)
3772 ),
3773 spec.names.as_ref(),
3774 )
3775 .await?;
3776
3777 let requested_limit = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
3778 let offset = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
3779 let ceiling = match (
3780 spec.max_rows,
3781 permission_limit(
3782 &gql_ctx.caller(),
3783 spec.names.as_ref(),
3784 &spec.schema_name,
3785 &spec.table_name,
3786 ),
3787 ) {
3788 (Some(configured), Some(granted)) => Some(configured.min(granted)),
3789 (only, None) | (None, only) => only,
3790 };
3791 let limit = match (requested_limit, ceiling) {
3792 (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
3793 (Some(requested), None) => Some(requested),
3794 (None, ceiling) => ceiling,
3795 };
3796
3797 let rows = format!("SELECT * FROM {}{}{}", source, where_sql, order_sql);
3798
3799 let mut inner = rows.clone();
3801 if let Some(limit) = limit {
3802 inner.push_str(&format!(" LIMIT {}", limit));
3803 }
3804 if let Some(offset) = offset {
3805 inner.push_str(&format!(" OFFSET {}", offset));
3806 }
3807
3808 let mut counted = rows;
3822 if let Some(limit) = requested_limit {
3823 counted.push_str(&format!(" LIMIT {}", limit));
3824 }
3825 if let Some(offset) = offset {
3826 counted.push_str(&format!(" OFFSET {}", offset));
3827 }
3828
3829 let mut wants_nodes = false;
3834 let mut wants_aggregate = false;
3835 let mut wanted: BTreeMap<String, Vec<String>> = BTreeMap::new();
3836 let mut counts: Vec<(String, String)> = Vec::new();
3839 for selection in ctx.field().selection_set() {
3840 match selection.name() {
3841 "nodes" => wants_nodes = true,
3842 "aggregate" => {
3843 wants_aggregate = true;
3844 for function in selection.selection_set() {
3845 if function.name() == "__typename" {
3847 continue;
3848 }
3849 if function.name() == "count" {
3850 counts.push((
3851 function.alias().unwrap_or("count").to_string(),
3852 count_expression(
3853 function,
3854 (&spec.schema_name, &spec.table_name),
3855 None,
3856 spec.names.as_ref(),
3857 ),
3858 ));
3859 continue;
3860 }
3861 let columns = wanted.entry(function.name().to_string()).or_default();
3864 for column in function.selection_set() {
3865 let name = column.name().to_string();
3866 if name != "__typename" && !columns.contains(&name) {
3867 columns.push(name);
3868 }
3869 }
3870 }
3871 }
3872 _ => {}
3873 }
3874 }
3875
3876 let mut result = async_graphql::indexmap::IndexMap::new();
3877
3878 if wants_aggregate {
3881 let mut parts = match counts.iter().any(|(key, _)| key == "count") {
3885 true => Vec::new(),
3886 false => vec!["'count', count(*)".to_string()],
3887 };
3888 for (key, expression) in &counts {
3889 parts.push(format!(
3890 "{}, {}",
3891 postrust_sql::quote_literal(key),
3892 expression
3893 ));
3894 }
3895 for (function, columns) in &wanted {
3896 let sql_function = agg::NUMERIC_AGGREGATES
3900 .iter()
3901 .chain(agg::ORDERED_AGGREGATES.iter())
3902 .find(|(name, _)| name == function)
3903 .map(|(name, _)| *name)
3904 .ok_or_else(|| {
3905 async_graphql::Error::new(format!("unknown aggregate \"{}\"", function))
3906 })?;
3907 let per_column: Vec<String> = columns
3908 .iter()
3909 .map(|field| {
3910 let source = spec
3911 .names
3912 .column_source(&spec.schema_name, &spec.table_name, field)
3913 .unwrap_or(field);
3914 format!(
3915 "{}, {}({})",
3916 postrust_sql::quote_literal(field),
3917 sql_function,
3918 postrust_sql::escape_ident(source)
3919 )
3920 })
3921 .collect();
3922 parts.push(format!(
3923 "'{}', json_build_object({})",
3924 sql_function,
3925 per_column.join(", ")
3926 ));
3927 }
3928
3929 let sql = format!(
3930 "SELECT json_build_object({}) FROM ({}) AS pgrst_agg",
3931 parts.join(", "),
3932 counted
3933 );
3934 let mut conn =
3935 begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
3936 let rows = execute_query_on(&mut conn, &sql, &bound_values).await?;
3937 conn.commit().await?;
3938 if let Some(first) = rows.into_iter().next() {
3939 if let Value::Object(map) = json_to_value(first) {
3940 result.insert(async_graphql::Name::new("aggregate"), Value::Object(map));
3941 }
3942 }
3943 }
3944
3945 if wants_nodes {
3946 let nodes = ctx
3952 .field()
3953 .selection_set()
3954 .find(|selection| selection.name() == "nodes")
3955 .ok_or_else(|| async_graphql::Error::new("nodes was asked for and then was not"))?;
3956
3957 let mut node_values = bound_values.clone();
3961 let (projection, row_column_types) = {
3962 let guard = gql_ctx
3963 .schema_cache
3964 .get()
3965 .await
3966 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
3967 let cache = guard
3968 .as_ref()
3969 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
3970 let qi = postrust_core::api_request::QualifiedIdentifier::new(
3971 &spec.schema_name,
3972 &spec.table_name,
3973 );
3974 let table = cache.get_table(&qi);
3975 let mut param_idx = node_values.len() + 1;
3976 let computed = match table {
3977 Some(table) => computed_projections(
3978 table,
3979 nodes.selection_set(),
3980 "src",
3981 spec.names.as_ref(),
3982 cache,
3983 &mut param_idx,
3984 &mut node_values,
3985 )?,
3986 None => Vec::new(),
3987 };
3988 let embeds = build_embed_expressions(
3989 &gql_ctx.caller(),
3990 cache,
3991 spec.relationships.as_ref(),
3992 &spec.type_name,
3993 "src",
3994 nodes.selection_set(),
3995 spec.max_rows,
3996 &mut 0,
3997 &mut param_idx,
3998 &mut node_values,
3999 spec.names.as_ref(),
4000 )?;
4001 let mut projection = table
4005 .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
4006 .unwrap_or_else(|| "src.*".to_string());
4007 for expression in &computed {
4008 projection.push_str(", ");
4009 projection.push_str(expression);
4010 }
4011 for (field_name, expression) in &embeds {
4012 projection.push_str(", ");
4013 projection.push_str(expression);
4014 projection.push_str(" AS ");
4015 projection.push_str(&postrust_sql::escape_ident(field_name));
4016 }
4017 (
4018 projection,
4019 table
4020 .map(|table| exposed_column_types(table, spec.names.as_ref()))
4021 .unwrap_or_default(),
4022 )
4023 };
4024
4025 let sql = format!(
4026 "SELECT {} FROM (SELECT {} FROM ({}) AS src) AS pgrst_nodes",
4027 row_json("pgrst_nodes", &row_column_types),
4028 projection,
4029 inner
4030 );
4031 let mut conn =
4032 begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
4033 let rows = execute_query_on(&mut conn, &sql, &node_values).await?;
4034 conn.commit().await?;
4035 result.insert(
4036 async_graphql::Name::new("nodes"),
4037 Value::List(rows.into_iter().map(json_to_value).collect()),
4038 );
4039 }
4040
4041 let _ = &spec.type_name;
4042 Ok(Value::Object(result))
4043}
4044
4045async fn resolve_query<'a>(
4047 ctx: &ResolverContext<'a>,
4048 spec: &QueryFieldSpec,
4049) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4050 Ok(rows_as_field_value(
4051 query_rows(ctx, spec).await?,
4052 spec.is_by_pk,
4053 ))
4054}
4055
4056async fn query_rows(
4062 ctx: &ResolverContext<'_>,
4063 spec: &QueryFieldSpec,
4064) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4065 let schema_name = spec.schema_name.as_str();
4066 let table_name = spec.table_name.as_str();
4067 let type_name = spec.type_name.as_str();
4068 let is_by_pk = spec.is_by_pk;
4069 let pk_columns = spec.pk_columns.as_slice();
4070 let max_rows = spec.max_rows;
4071 let relationships = spec.relationships.as_ref();
4072
4073 let pool = ctx.data::<PgPool>()?;
4074 let gql_ctx = ctx.data::<GraphQLContext>()?;
4075
4076 debug!("Resolving query for table: {}", table_name);
4077
4078 let requested_limit: Option<i64> = ctx.args.try_get("limit").ok().and_then(|v| v.i64().ok());
4080
4081 let offset: Option<i64> = ctx.args.try_get("offset").ok().and_then(|v| v.i64().ok());
4082
4083 let limit: Option<i64> = if is_by_pk {
4088 Some(1)
4089 } else {
4090 let ceiling = match (
4094 max_rows,
4095 permission_limit(
4096 &gql_ctx.caller(),
4097 spec.names.as_ref(),
4098 schema_name,
4099 table_name,
4100 ),
4101 ) {
4102 (Some(configured), Some(granted)) => Some(configured.min(granted)),
4103 (only, None) | (None, only) => only,
4104 };
4105 match (requested_limit, ceiling) {
4106 (Some(requested), Some(ceiling)) => Some(requested.min(ceiling)),
4107 (Some(requested), None) => Some(requested),
4108 (None, ceiling) => ceiling,
4109 }
4110 };
4111
4112 let mut where_sql = String::new();
4120 let mut bound_values: Vec<serde_json::Value> = Vec::new();
4121
4122 let source = match &spec.call {
4126 None => format!(
4132 "{}.{} AS {}",
4133 postrust_sql::escape_ident(schema_name),
4134 postrust_sql::escape_ident(table_name),
4135 postrust_sql::escape_ident(READ_ROW)
4136 ),
4137 Some(call) => function_source(ctx, call, table_name, &mut bound_values)?,
4138 };
4139
4140 let source_ref = match &spec.call {
4143 None => postrust_sql::escape_ident(READ_ROW),
4144 Some(_) => postrust_sql::escape_ident(table_name),
4145 };
4146
4147 if is_by_pk {
4148 if pk_columns.is_empty() {
4149 return Err(async_graphql::Error::new(format!(
4150 "\"{}\" has no primary key, so it cannot be queried by key",
4151 table_name
4152 )));
4153 }
4154
4155 let mut conditions = Vec::with_capacity(pk_columns.len());
4156 for (idx, (col_name, pg_type)) in pk_columns.iter().enumerate() {
4157 let field = spec
4158 .names
4159 .column(schema_name, table_name, col_name)
4160 .unwrap_or(col_name);
4161 let value = ctx.args.try_get(field).map_err(|_| {
4162 async_graphql::Error::new(format!(
4163 "missing required primary key argument \"{}\"",
4164 field
4165 ))
4166 })?;
4167
4168 conditions.push(format!(
4169 "{} = ${}::{}",
4170 postrust_sql::escape_ident(col_name),
4171 idx + 1,
4172 pg_type
4173 ));
4174 bound_values.push(accessor_to_json(&value));
4175 }
4176 where_sql = format!(" WHERE {}", conditions.join(" AND "));
4177 }
4178
4179 {
4184 let requested = match is_by_pk {
4185 true => None,
4186 false => ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v)),
4187 };
4188 let permission = permission_predicate(
4189 &gql_ctx.caller(),
4190 spec.names.as_ref(),
4191 schema_name,
4192 table_name,
4193 )?;
4194
4195 if let Some(predicate) = and_predicates(requested, permission) {
4196 let guard = gql_ctx
4197 .schema_cache
4198 .get()
4199 .await
4200 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4201 let cache = guard
4202 .as_ref()
4203 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4204 let scope = match &spec.call {
4205 None => WhereScope::table(schema_name, table_name, type_name, spec.names.as_ref())
4206 .under_alias(READ_ROW)
4207 .with_resolution(cache, relationships)
4208 .for_caller(gql_ctx.caller()),
4209 Some(_) => WhereScope::for_alias(
4212 schema_name,
4213 table_name,
4214 table_name,
4215 type_name,
4216 cache,
4217 relationships,
4218 spec.names.as_ref(),
4219 )
4220 .for_caller(gql_ctx.caller()),
4221 };
4222 let (filter_sql, filter_values) =
4223 build_where_clause(Some(&predicate), bound_values.len() + 1, &scope)?;
4224 if !filter_sql.is_empty() {
4225 where_sql = match where_sql.is_empty() {
4228 true => format!(" {}", filter_sql),
4229 false => format!(
4230 "{} AND ({})",
4231 where_sql,
4232 filter_sql.trim_start_matches("WHERE ")
4233 ),
4234 };
4235 bound_values.extend(filter_values);
4236 }
4237 }
4238 }
4239
4240 let (order_sql, distinct_on) = if is_by_pk {
4243 (String::new(), Vec::new())
4244 } else {
4245 (
4246 build_order_by_clause(
4247 ctx,
4248 &gql_ctx.schema_cache,
4249 schema_name,
4250 table_name,
4251 type_name,
4252 relationships,
4253 &source_ref,
4254 spec.names.as_ref(),
4255 )
4256 .await?,
4257 build_distinct_on(
4258 ctx,
4259 &gql_ctx.schema_cache,
4260 schema_name,
4261 table_name,
4262 spec.names.as_ref(),
4263 )
4264 .await?,
4265 )
4266 };
4267
4268 let (distinct_sql, order_sql) = {
4276 let written = order_sql.strip_prefix(" ORDER BY ");
4277 let (prefix, order) = distinct_on_clause(&distinct_on, written).map_err(|error| {
4280 at_path(
4281 error,
4282 &format!(
4283 "$.selectionSet.{}.args",
4284 ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4285 ),
4286 )
4287 })?;
4288 (
4289 prefix,
4290 match order {
4291 Some(order) => format!(" ORDER BY {}", order),
4292 None => String::new(),
4293 },
4294 )
4295 };
4296
4297 let (computed, row_column_types) = {
4300 let guard = gql_ctx
4301 .schema_cache
4302 .get()
4303 .await
4304 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4305 match guard.as_ref() {
4306 Some(cache) => {
4307 let qi =
4308 postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
4309 match cache.get_table(&qi) {
4310 Some(table) => {
4311 let mut param_idx = bound_values.len() + 1;
4312 (
4313 computed_projections(
4314 table,
4315 ctx.field().selection_set(),
4316 "src",
4317 spec.names.as_ref(),
4318 cache,
4319 &mut param_idx,
4320 &mut bound_values,
4321 )?,
4322 exposed_column_types(table, spec.names.as_ref()),
4323 )
4324 }
4325 None => (Vec::new(), HashMap::new()),
4326 }
4327 }
4328 None => (Vec::new(), HashMap::new()),
4329 }
4330 };
4331 let mut inner = format!(
4335 "SELECT {}* FROM {}{}{}",
4336 distinct_sql, source, where_sql, order_sql
4337 );
4338
4339 if let Some(limit) = limit {
4340 inner.push_str(&format!(" LIMIT {}", limit));
4341 }
4342
4343 if let Some(offset) = offset {
4344 inner.push_str(&format!(" OFFSET {}", offset));
4345 }
4346
4347 let embed_expressions = {
4350 let guard = gql_ctx
4351 .schema_cache
4352 .get()
4353 .await
4354 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4355 match guard.as_ref() {
4356 Some(cache) => {
4357 let mut param_idx = bound_values.len() + 1;
4362 build_embed_expressions(
4363 &gql_ctx.caller(),
4364 cache,
4365 relationships,
4366 type_name,
4367 "src",
4368 ctx.field().selection_set(),
4369 max_rows,
4370 &mut 0,
4371 &mut param_idx,
4372 &mut bound_values,
4373 spec.names.as_ref(),
4374 )?
4375 }
4376 None => Vec::new(),
4377 }
4378 };
4379
4380 let renamed = {
4388 let guard = gql_ctx
4389 .schema_cache
4390 .get()
4391 .await
4392 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4393 guard
4394 .as_ref()
4395 .and_then(|cache| {
4396 cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
4397 schema_name,
4398 table_name,
4399 ))
4400 })
4401 .and_then(|table| rename_projection(table, "src", spec.names.as_ref()))
4402 };
4403 let inner = if embed_expressions.is_empty() && computed.is_empty() && renamed.is_none() {
4404 inner
4405 } else {
4406 let mut projection = renamed.unwrap_or_else(|| "src.*".to_string());
4407 for expression in &computed {
4408 projection.push_str(", ");
4409 projection.push_str(expression);
4410 }
4411 for (field_name, expression) in &embed_expressions {
4412 projection.push_str(", ");
4413 projection.push_str(expression);
4414 projection.push_str(" AS ");
4415 projection.push_str(&postrust_sql::escape_ident(field_name));
4416 }
4417 format!("SELECT {} FROM ({}) AS src", projection, inner)
4418 };
4419
4420 let sql = format!(
4421 "SELECT {} FROM ({}) t",
4422 row_json("t", &row_column_types),
4423 inner
4424 );
4425
4426 let mut tx = begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?;
4430
4431 let mut result = execute_query_on(&mut tx, &sql, &bound_values).await?;
4433
4434 if embed_expressions.is_empty() {
4436 let guard = gql_ctx
4437 .schema_cache
4438 .get()
4439 .await
4440 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4441 let cache = guard
4442 .as_ref()
4443 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4444
4445 let embed_ctx = EmbedContext {
4446 schema_cache: cache,
4447 relationships,
4448 max_rows,
4449 };
4450
4451 embed_relationships(&mut tx, &embed_ctx, type_name, ctx.field(), &mut result).await?;
4452 }
4453
4454 tx.commit().await?;
4455
4456 Ok(result)
4457}
4458
4459fn rows_as_field_value<'a>(rows: Vec<serde_json::Value>, is_by_pk: bool) -> Option<FieldValue<'a>> {
4464 match is_by_pk {
4465 true => rows
4466 .into_iter()
4467 .next()
4468 .map(|v| FieldValue::value(json_to_value(v))),
4469 false => Some(FieldValue::list(
4470 rows.into_iter()
4471 .map(|v| FieldValue::value(json_to_value(v))),
4472 )),
4473 }
4474}
4475
4476#[allow(clippy::too_many_arguments)]
4478async fn resolve_mutation<'a>(
4479 ctx: &ResolverContext<'a>,
4480 schema_name: &str,
4481 table_name: &str,
4482 mutation_type: MutationType,
4483 pk_columns: &[(String, String)],
4484 relationships: &Arc<HashMap<String, Vec<RelationshipField>>>,
4485 type_names: &Arc<HashMap<(String, String), String>>,
4486 names: &Arc<crate::names::NameOverrides>,
4487 max_rows: Option<i64>,
4488) -> Result<Option<FieldValue<'a>>, async_graphql::Error> {
4489 let pool = ctx.data::<PgPool>()?;
4490 let gql_ctx = ctx.data::<GraphQLContext>()?;
4491
4492 debug!(
4493 "Resolving mutation for table: {} type: {:?}",
4494 table_name, mutation_type
4495 );
4496
4497 let type_name = type_names
4503 .get(&(schema_name.to_string(), table_name.to_string()))
4504 .cloned()
4505 .unwrap_or_else(|| table_name.to_string());
4506
4507 let by_key = matches!(
4508 mutation_type,
4509 MutationType::InsertOne | MutationType::UpdateByPk | MutationType::DeleteByPk
4510 );
4511 let returning = if by_key {
4512 Some(ctx.field())
4513 } else {
4514 ctx.field()
4515 .selection_set()
4516 .find(|selection| selection.name() == "returning")
4517 };
4518
4519 if mutation_type == MutationType::UpdateMany {
4524 let updates = ctx
4525 .args
4526 .try_get("updates")
4527 .ok()
4528 .map(|v| accessor_to_json(&v))
4529 .unwrap_or(serde_json::Value::Null);
4530 let serde_json::Value::Array(entries) = updates else {
4531 return Err(async_graphql::Error::new(
4532 "\"updates\" takes a list of {where, _set, …} objects",
4533 ));
4534 };
4535 let column_types = column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await;
4536 let mut answers: Vec<FieldValue<'_>> = Vec::with_capacity(entries.len());
4537 for entry in entries {
4538 let serde_json::Value::Object(entry) = entry else {
4539 return Err(async_graphql::Error::new(
4540 "each update is an object of {where, _set, …}",
4541 ));
4542 };
4543 let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4544 .iter()
4545 .filter_map(|name| entry.get(*name).map(|value| (*name, value.clone())))
4546 .filter(|(_, value)| !value.is_null())
4547 .collect();
4548 let rows = execute_update(
4549 pool,
4550 gql_ctx,
4551 schema_name,
4552 table_name,
4553 &type_name,
4554 operators,
4555 column_types.clone(),
4556 entry.get("where").cloned(),
4557 relationships.as_ref(),
4558 names.as_ref(),
4559 )
4560 .await?;
4561 let affected = rows.len();
4562 let rows = match returning {
4563 Some(returning) if !rows.is_empty() => {
4564 reread_returning(
4565 pool,
4566 gql_ctx,
4567 schema_name,
4568 table_name,
4569 &type_name,
4570 rows,
4571 returning,
4572 relationships.as_ref(),
4573 names.as_ref(),
4574 max_rows,
4575 )
4576 .await?
4577 }
4578 _ => rows,
4579 };
4580 answers.extend(mutation_result(rows, affected, false));
4581 }
4582 return Ok(Some(FieldValue::list(answers)));
4583 }
4584
4585 let (result, affected) = match mutation_type {
4586 MutationType::Insert | MutationType::InsertOne => {
4587 let objects = ctx
4588 .args
4589 .try_get("objects")
4590 .ok()
4591 .map(|v| accessor_to_json(&v))
4592 .or_else(|| {
4593 ctx.args
4596 .try_get("object")
4597 .ok()
4598 .map(|v| serde_json::Value::Array(vec![accessor_to_json(&v)]))
4599 })
4600 .unwrap_or_else(|| serde_json::Value::Array(vec![]));
4601
4602 let guard = gql_ctx
4603 .schema_cache
4604 .get()
4605 .await
4606 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
4607 let cache = guard
4608 .as_ref()
4609 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
4610 let here = format!(
4614 "$.selectionSet.{}",
4615 ctx.ctx.field().alias().unwrap_or(ctx.ctx.field().name())
4616 );
4617 let named = match mutation_type {
4620 MutationType::InsertOne => "object",
4621 _ => "objects",
4622 };
4623 let context = InsertContext {
4624 on_conflict: ctx
4625 .args
4626 .try_get("on_conflict")
4627 .ok()
4628 .map(|v| accessor_to_json(&v))
4629 .filter(|v| !v.is_null()),
4630 cache,
4631 relationships: relationships.as_ref(),
4632 type_names: type_names.as_ref(),
4633 names: names.as_ref(),
4634 caller: gql_ctx.caller(),
4635 row: String::new(),
4638 objects: format!("{}.args.{}", here, named),
4639 conflict: format!("{}.args.on_conflict", here),
4640 };
4641
4642 execute_insert(
4643 pool,
4644 gql_ctx,
4645 schema_name,
4646 table_name,
4647 objects,
4648 &context,
4649 mutation_type == MutationType::InsertOne,
4650 )
4651 .await?
4652 }
4653 MutationType::UpdateMany | MutationType::Update | MutationType::UpdateByPk => {
4656 let operators: Vec<(&'static str, serde_json::Value)> = UPDATE_OPERATORS
4660 .iter()
4661 .filter_map(|name| {
4662 ctx.args
4663 .try_get(name)
4664 .ok()
4665 .map(|v| (*name, accessor_to_json(&v)))
4666 })
4667 .filter(|(_, value)| !value.is_null())
4668 .collect();
4669
4670 let where_clause = if mutation_type == MutationType::UpdateByPk {
4671 Some(pk_where_from_args(
4672 ctx,
4673 schema_name,
4674 table_name,
4675 pk_columns,
4676 names.as_ref(),
4677 )?)
4678 } else {
4679 ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4680 };
4681 let where_clause = and_predicates(
4685 where_clause,
4686 permission_filter(
4687 &gql_ctx.caller(),
4688 names.as_ref(),
4689 schema_name,
4690 table_name,
4691 crate::role::Verb::Update,
4692 )?,
4693 );
4694
4695 execute_update(
4696 pool,
4697 gql_ctx,
4698 schema_name,
4699 table_name,
4700 &type_name,
4701 operators,
4702 column_types_of(&gql_ctx.schema_cache, schema_name, table_name).await,
4703 where_clause,
4704 relationships.as_ref(),
4705 names.as_ref(),
4706 )
4707 .await
4708 .map(|rows| {
4709 let count = rows.len();
4710 (rows, count)
4711 })?
4712 }
4713 MutationType::Delete | MutationType::DeleteByPk => {
4714 let where_clause = if mutation_type == MutationType::DeleteByPk {
4715 Some(pk_where_from_args(
4716 ctx,
4717 schema_name,
4718 table_name,
4719 pk_columns,
4720 names.as_ref(),
4721 )?)
4722 } else {
4723 ctx.args.try_get("where").ok().map(|v| accessor_to_json(&v))
4724 };
4725 let where_clause = and_predicates(
4726 where_clause,
4727 permission_filter(
4728 &gql_ctx.caller(),
4729 names.as_ref(),
4730 schema_name,
4731 table_name,
4732 crate::role::Verb::Delete,
4733 )?,
4734 );
4735
4736 execute_delete(
4737 pool,
4738 gql_ctx,
4739 schema_name,
4740 table_name,
4741 &type_name,
4742 where_clause,
4743 returning,
4744 relationships.as_ref(),
4745 names.as_ref(),
4746 max_rows,
4747 )
4748 .await
4749 .map(|rows| {
4750 let count = rows.len();
4751 (rows, count)
4752 })?
4753 }
4754 };
4755
4756 let is_delete = matches!(
4757 mutation_type,
4758 MutationType::Delete | MutationType::DeleteByPk
4759 );
4760 let result = match returning {
4761 Some(returning) if !result.is_empty() && !is_delete => {
4762 reread_returning(
4763 pool,
4764 gql_ctx,
4765 schema_name,
4766 table_name,
4767 &type_name,
4768 result,
4769 returning,
4770 relationships.as_ref(),
4771 names.as_ref(),
4772 max_rows,
4773 )
4774 .await?
4775 }
4776 _ => result,
4777 };
4778
4779 Ok(mutation_result(result, affected, by_key))
4780}
4781
4782async fn write_tx<'a>(
4790 gql_ctx: &'a GraphQLContext,
4791 pool: &PgPool,
4792) -> Result<
4793 tokio::sync::MutexGuard<'a, Option<sqlx::Transaction<'static, sqlx::Postgres>>>,
4794 async_graphql::Error,
4795> {
4796 let mut guard = gql_ctx.write.lock().await;
4797 if guard.is_none() {
4798 *guard = Some(begin_with_session(pool, gql_ctx.role(), &gql_ctx.session_settings()).await?);
4799 }
4800 Ok(guard)
4801}
4802
4803async fn begin_with_session(
4809 pool: &PgPool,
4810 role: &str,
4811 settings: &[(String, String)],
4812) -> Result<sqlx::Transaction<'static, sqlx::Postgres>, async_graphql::Error> {
4813 let mut tx = pool.begin().await?;
4814
4815 if !settings.is_empty() {
4825 let calls = (0..settings.len())
4826 .map(|i| format!("set_config(${}, ${}, true)", i * 2 + 1, i * 2 + 2))
4827 .collect::<Vec<_>>()
4828 .join(", ");
4829 let sql = format!("SELECT {calls}");
4830 let mut query = sqlx::query_scalar::<_, String>(&sql);
4831 for (name, value) in settings {
4832 query = query.bind(name).bind(value);
4833 }
4834 query.fetch_optional(&mut *tx).await?;
4835 }
4836
4837 sqlx::query(&format!(
4838 "SET LOCAL ROLE {}",
4839 postrust_sql::escape_ident(role)
4840 ))
4841 .execute(&mut *tx)
4842 .await?;
4843 Ok(tx)
4844}
4845
4846fn row_json(expr: &str, column_types: &HashMap<String, String>) -> String {
4857 let mut shapes: Vec<(&String, &String)> = column_types
4858 .iter()
4859 .filter(|(_, pg_type)| matches!(pg_type.as_str(), "geometry" | "geography"))
4860 .collect();
4861 if shapes.is_empty() {
4862 return format!("row_to_json({})", expr);
4863 }
4864 shapes.sort();
4865
4866 let overrides: Vec<String> = shapes
4867 .iter()
4868 .map(|(name, _)| {
4869 format!(
4870 "{}, ST_AsGeoJSON({}.{}, 9, 4)::jsonb",
4876 postrust_sql::quote_literal(name),
4877 expr,
4878 postrust_sql::escape_ident(name)
4879 )
4880 })
4881 .collect();
4882 format!(
4883 "(to_jsonb({}) || jsonb_build_object({}))",
4884 expr,
4885 overrides.join(", ")
4886 )
4887}
4888
4889async fn execute_query_on(
4892 conn: &mut sqlx::PgConnection,
4893 sql: &str,
4894 params: &[serde_json::Value],
4895) -> Result<Vec<serde_json::Value>, async_graphql::Error> {
4896 use sqlx::Row;
4897
4898 trace!("Executing SQL: {}", sql);
4899
4900 let mut query = sqlx::query(sql);
4902 for param in params {
4903 query = bind_json_value(query, param);
4904 }
4905 let rows = query.fetch_all(&mut *conn).await.map_err(database_error)?;
4906
4907 let results: Vec<serde_json::Value> = rows
4910 .into_iter()
4911 .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
4912 .collect();
4913
4914 Ok(results)
4915}
4916
4917fn check_geojson(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4930 fn refuse<T>(message: &str) -> Result<T, async_graphql::Error> {
4931 Err(coded_error("parse-failed", message))
4932 }
4933
4934 fn position(
4935 value: &serde_json::Value,
4936 ) -> Result<&Vec<serde_json::Value>, async_graphql::Error> {
4937 match value.as_array() {
4938 Some(items) if items.len() >= 2 && items.iter().all(|i| i.is_number()) => Ok(items),
4939 _ => refuse("A Position needs at least 2 elements"),
4940 }
4941 }
4942
4943 fn positions(
4944 value: Option<&serde_json::Value>,
4945 least: usize,
4946 what: &str,
4947 ) -> Result<Vec<Vec<serde_json::Value>>, async_graphql::Error> {
4948 let items = match value.and_then(|v| v.as_array()) {
4949 Some(items) => items,
4950 None => return refuse(&format!("A {} needs at least {} Positions", what, least)),
4951 };
4952 if items.len() < least {
4953 return refuse(&format!("A {} needs at least {} Positions", what, least));
4954 }
4955 items.iter().map(|item| position(item).cloned()).collect()
4956 }
4957
4958 fn ring(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4959 let points = positions(Some(value), 4, "LinearRing")?;
4960 match (points.first(), points.last()) {
4961 (Some(first), Some(last)) if first == last => Ok(()),
4962 _ => refuse("the first and last locations have to be equal for a LinearRing"),
4963 }
4964 }
4965
4966 fn polygon(value: &serde_json::Value) -> Result<(), async_graphql::Error> {
4967 let Some(rings) = value.as_array() else {
4968 return refuse("A LinearRing needs at least 4 Positions");
4969 };
4970 rings.iter().try_for_each(ring)
4971 }
4972
4973 let serde_json::Value::Object(map) = value else {
4974 return Ok(());
4975 };
4976 let Some(kind) = map.get("type").and_then(|v| v.as_str()) else {
4977 return Ok(());
4978 };
4979 let coordinates = map.get("coordinates");
4980 match kind {
4981 "Point" => {
4982 position(coordinates.unwrap_or(&serde_json::Value::Null))?;
4983 }
4984 "MultiPoint" => {
4985 positions(coordinates, 0, "MultiPoint")?;
4986 }
4987 "LineString" => {
4988 positions(coordinates, 2, "LineString")?;
4989 }
4990 "MultiLineString" => {
4991 for line in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4992 positions(Some(line), 2, "LineString")?;
4993 }
4994 }
4995 "Polygon" => polygon(coordinates.unwrap_or(&serde_json::Value::Null))?,
4996 "MultiPolygon" => {
4997 for one in coordinates.and_then(|v| v.as_array()).into_iter().flatten() {
4998 polygon(one)?;
4999 }
5000 }
5001 "GeometryCollection" => {
5002 for geometry in map
5003 .get("geometries")
5004 .and_then(|v| v.as_array())
5005 .into_iter()
5006 .flatten()
5007 {
5008 check_geojson(geometry)?;
5009 }
5010 }
5011 other => return refuse(&format!("unexpected geometry type: {}", other)),
5012 }
5013 Ok(())
5014}
5015
5016fn computed_call(
5024 definition: &postrust_core::schema_cache::ComputedColumn,
5025 row: &str,
5026 row_by_name: &str,
5027) -> String {
5028 let function = format!(
5029 "{}.{}",
5030 postrust_sql::escape_ident(&definition.function.schema),
5031 postrust_sql::escape_ident(&definition.function.name)
5032 );
5033 match (&definition.session_argument, &definition.row_argument) {
5034 (Some(session), Some(row_argument)) => format!(
5035 "{}({} => {}, {} => {})",
5036 function,
5037 postrust_sql::escape_ident(row_argument),
5038 row_by_name,
5039 postrust_sql::escape_ident(session),
5040 SESSION_ARGUMENT
5041 ),
5042 _ => format!("{}({})", function, row),
5043 }
5044}
5045
5046const SESSION_ARGUMENT: &str = "coalesce(current_setting('hasura.session', true), '{}')::json";
5052
5053const UPDATE_OPERATORS: [&str; 7] = [
5056 "_set",
5057 "_inc",
5058 "_append",
5059 "_prepend",
5060 "_delete_key",
5061 "_delete_elem",
5062 "_delete_at_path",
5063];
5064
5065const READ_ROW: &str = "pgrst_src";
5071
5072const WRITTEN_ROW: &str = "pgrst_row";
5077
5078fn write_cast(column_types: &HashMap<String, String>, column: &str) -> String {
5086 match column_types.get(column) {
5087 Some(pg_type) => format!("::{}", pg_type),
5088 None => String::new(),
5089 }
5090}
5091
5092fn write_operand(
5099 column_types: &HashMap<String, String>,
5100 column: &str,
5101 value: &serde_json::Value,
5102) -> serde_json::Value {
5103 match column_types.get(column).map(String::as_str) {
5104 Some("json") | Some("jsonb") => match value {
5105 serde_json::Value::String(_)
5106 | serde_json::Value::Number(_)
5107 | serde_json::Value::Bool(_) => serde_json::Value::String(value.to_string()),
5108 other => other.clone(),
5109 },
5110 Some(pg_type) if is_array_type(pg_type) => match value {
5115 serde_json::Value::Array(_) => serde_json::Value::String(array_literal(value)),
5116 other => other.clone(),
5117 },
5118 _ => value.clone(),
5119 }
5120}
5121
5122fn is_array_type(pg_type: &str) -> bool {
5127 pg_type.ends_with("[]") || pg_type.starts_with('_')
5128}
5129
5130fn array_literal(value: &serde_json::Value) -> String {
5136 fn element(value: &serde_json::Value) -> String {
5137 match value {
5138 serde_json::Value::Null => "NULL".to_string(),
5139 serde_json::Value::Array(_) => array_literal(value),
5140 serde_json::Value::String(text) => {
5141 format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
5142 }
5143 other => format!("\"{}\"", other.to_string().replace('"', "\\\"")),
5144 }
5145 }
5146 let items = match value {
5147 serde_json::Value::Array(items) => items,
5148 other => return element(other),
5149 };
5150 format!(
5151 "{{{}}}",
5152 items.iter().map(element).collect::<Vec<_>>().join(",")
5153 )
5154}
5155
5156fn write_expression(
5167 column_types: &HashMap<String, String>,
5168 column: &str,
5169 value: &serde_json::Value,
5170 placeholder: &str,
5171) -> String {
5172 let pg_type = column_types.get(column).map(String::as_str);
5173 if value.is_object() && matches!(pg_type, Some("geometry") | Some("geography")) {
5174 return match pg_type {
5175 Some("geography") => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
5176 _ => format!("ST_GeomFromGeoJSON({})", placeholder),
5177 };
5178 }
5179 if pg_type == Some("money") && value.is_number() {
5185 return format!("{}::numeric::money", placeholder);
5186 }
5187 format!("{}{}", placeholder, write_cast(column_types, column))
5188}
5189
5190async fn column_types_of(
5192 schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
5193 schema_name: &str,
5194 table_name: &str,
5195) -> HashMap<String, String> {
5196 let Ok(guard) = schema_cache.get().await else {
5197 return HashMap::new();
5198 };
5199 let Some(cache) = guard.as_ref() else {
5200 return HashMap::new();
5201 };
5202 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5203 match cache.get_table(&qi) {
5204 Some(table) => table
5205 .columns
5206 .values()
5207 .map(|c| (c.name.clone(), c.nominal_type.clone()))
5208 .collect(),
5209 None => HashMap::new(),
5210 }
5211}
5212
5213fn insert_row<'life>(
5231 conn: &'life mut sqlx::PgConnection,
5232 schema_name: &'life str,
5233 table_name: &'life str,
5234 object: serde_json::Map<String, serde_json::Value>,
5235 context: &'life InsertContext<'life>,
5236 written_count: &'life mut usize,
5237) -> std::pin::Pin<
5238 Box<
5239 dyn std::future::Future<Output = Result<serde_json::Value, async_graphql::Error>>
5240 + Send
5241 + 'life,
5242 >,
5243> {
5244 Box::pin(async move {
5245 use sqlx::Row;
5246
5247 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5248 let table = context.cache.get_table(&qi).ok_or_else(|| {
5249 async_graphql::Error::new(format!("unknown table \"{}\"", table_name))
5250 })?;
5251 let column_types: HashMap<String, String> = table
5252 .columns
5253 .values()
5254 .map(|c| (c.name.clone(), c.nominal_type.clone()))
5255 .collect();
5256
5257 let type_name = context
5259 .type_names
5260 .get(&(schema_name.to_string(), table_name.to_string()));
5261 let relationships: &[RelationshipField] = type_name
5262 .and_then(|name| context.relationships.get(name))
5263 .map(|r| r.as_slice())
5264 .unwrap_or(&[]);
5265
5266 let mut columns = serde_json::Map::new();
5267 type Nested<'r> = (
5268 &'r RelationshipField,
5269 serde_json::Value,
5270 Option<serde_json::Value>,
5271 );
5272 let mut to_one: Vec<Nested> = Vec::new();
5273 let mut to_many: Vec<Nested> = Vec::new();
5274
5275 for (key, value) in object {
5276 match relationships.iter().find(|r| r.name == key) {
5277 None => {
5278 columns.insert(
5281 table_column_for(context.names, table, &key).to_string(),
5282 value,
5283 );
5284 }
5285 Some(_) if value.is_null() => {
5286 continue;
5290 }
5291 Some(relationship) => {
5292 let (data, conflict) = match &value {
5296 serde_json::Value::Object(map) => (
5297 map.get("data").cloned().unwrap_or(value.clone()),
5298 map.get("on_conflict").cloned().filter(|v| !v.is_null()),
5299 ),
5300 other => (other.clone(), None),
5301 };
5302 if child_holds_the_key(&relationship.relationship) {
5309 to_many.push((relationship, data, conflict));
5310 } else {
5311 to_one.push((relationship, data, conflict));
5312 }
5313 }
5314 }
5315 }
5316
5317 for (column, value) in &columns {
5320 if matches!(
5321 column_types.get(column).map(String::as_str),
5322 Some("geometry") | Some("geography")
5323 ) {
5324 check_geojson(value).map_err(|error| {
5327 let field = context
5328 .names
5329 .column(schema_name, table_name, column)
5330 .unwrap_or(column);
5331 at_path(error, &format!("{}.{}", context.row, field))
5332 })?;
5333 }
5334 }
5335
5336 for (relationship, data, conflict) in to_one {
5339 let plan =
5340 postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5341 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5342 let serde_json::Value::Object(child) = data else {
5343 return Err(async_graphql::Error::new(format!(
5344 "\"{}\" takes an object to insert",
5345 relationship.name
5346 )));
5347 };
5348 for (local, _) in &plan.columns {
5352 if columns.get(local).is_some_and(|v| !v.is_null()) {
5353 return Err(at_path(
5354 validation_error(format!(
5355 "cannot insert object relationship \"{}\" as \"{}\" column \
5356 values are already determined",
5357 relationship.name, local
5358 )),
5359 &format!("{}.{}", context.row, relationship.name),
5360 ));
5361 }
5362 }
5363 let under = format!("{}.{}", context.row, relationship.name);
5366 let nested = InsertContext {
5367 on_conflict: conflict,
5368 cache: context.cache,
5369 relationships: context.relationships,
5370 type_names: context.type_names,
5371 names: context.names,
5372 caller: context.caller,
5373 row: format!("{}.data", under),
5374 objects: format!("{}.data", under),
5375 conflict: format!("{}.on_conflict", under),
5376 };
5377 let written = insert_row(
5378 conn,
5379 &plan.foreign_schema,
5380 &plan.foreign_table,
5381 child,
5382 &nested,
5383 written_count,
5384 )
5385 .await?;
5386 for (local, foreign) in &plan.columns {
5387 if let Some(value) = written.get(foreign) {
5388 columns.insert(local.clone(), value.clone());
5389 }
5390 }
5391 }
5392
5393 for (column, value) in crate::role::presets(
5399 &context.caller,
5400 context.names,
5401 schema_name,
5402 table_name,
5403 crate::role::Verb::Insert,
5404 )
5405 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?
5406 {
5407 columns.insert(column, value);
5408 }
5409
5410 let column_names: Vec<&str> = columns.keys().map(|k| k.as_str()).collect();
5416 let mut conflict_values: Vec<serde_json::Value> = Vec::new();
5417 let conflict_sql = match &context.on_conflict {
5418 Some(serde_json::Value::Object(spec)) => {
5419 let constraint = spec.get("constraint").and_then(|v| v.as_str());
5420 let Some(constraint) = constraint else {
5421 return Err(async_graphql::Error::new(
5422 "on_conflict needs a constraint to resolve against",
5423 ));
5424 };
5425 if !table
5426 .unique_constraints
5427 .iter()
5428 .any(|(name, _)| name == constraint)
5429 {
5430 return Err(async_graphql::Error::new(format!(
5431 "\"{}\" is not a unique constraint of \"{}\"",
5432 constraint, table_name
5433 )));
5434 }
5435 let updates: Vec<&str> = spec
5436 .get("update_columns")
5437 .and_then(|v| v.as_array())
5438 .map(|items| items.iter().filter_map(|i| i.as_str()).collect())
5439 .unwrap_or_default();
5440 if updates.contains(&PLACEHOLDER_COLUMN) {
5444 return Err(at_path(
5445 coded_error("validation-failed", "erroneous column name"),
5446 &context.conflict,
5447 ));
5448 }
5449
5450 if updates.is_empty() {
5451 format!(
5452 " ON CONFLICT ON CONSTRAINT {} DO NOTHING",
5453 postrust_sql::escape_ident(constraint)
5454 )
5455 } else {
5456 let preset = crate::role::presets(
5462 &context.caller,
5463 context.names,
5464 schema_name,
5465 table_name,
5466 crate::role::Verb::Update,
5467 )
5468 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5469
5470 let mut param_idx = column_names.len() + 1;
5474 let mut assignments: Vec<String> =
5475 Vec::with_capacity(preset.len() + updates.len());
5476 for (column, value) in &preset {
5477 assignments.push(format!(
5478 "{} = {}",
5479 postrust_sql::escape_ident(column),
5480 write_expression(
5481 &column_types,
5482 column,
5483 value,
5484 &format!("${}", param_idx)
5485 )
5486 ));
5487 conflict_values.push(write_operand(&column_types, column, value));
5488 param_idx += 1;
5489 }
5490 for field in &updates {
5491 let column = table_column_for(context.names, table, field);
5492 if preset.iter().any(|(written, _)| written == column) {
5493 continue;
5494 }
5495 assignments.push(format!(
5496 "{} = EXCLUDED.{}",
5497 postrust_sql::escape_ident(column),
5498 postrust_sql::escape_ident(column)
5499 ));
5500 }
5501 let granted = permission_filter(
5514 &context.caller,
5515 context.names,
5516 schema_name,
5517 table_name,
5518 crate::role::Verb::Update,
5519 )?;
5520 let requested = spec.get("where").filter(|f| !f.is_null()).cloned();
5521 let condition = match and_predicates(requested, granted) {
5522 Some(filter) => {
5523 let filter = &filter;
5524 let type_name = context
5525 .type_names
5526 .get(&(schema_name.to_string(), table_name.to_string()))
5527 .map(String::as_str)
5528 .unwrap_or(table_name);
5529 let scope = WhereScope::table(
5530 schema_name,
5531 table_name,
5532 type_name,
5533 context.names,
5534 )
5535 .under_alias(WRITTEN_ROW)
5536 .with_resolution(context.cache, context.relationships);
5537 let mut alias_counter = 0usize;
5538 build_condition(
5539 filter,
5540 &scope,
5541 &mut param_idx,
5542 &mut conflict_values,
5543 &mut alias_counter,
5544 )?
5545 .map(|sql| format!(" WHERE {}", sql))
5546 .unwrap_or_default()
5547 }
5548 None => String::new(),
5549 };
5550 format!(
5551 " ON CONFLICT ON CONSTRAINT {} DO UPDATE SET {}{}",
5552 postrust_sql::escape_ident(constraint),
5553 assignments.join(", "),
5554 condition
5555 )
5556 }
5557 }
5558 _ => String::new(),
5559 };
5560
5561 let qualified_table = postrust_sql::escape_ident(WRITTEN_ROW);
5567
5568 let check_scope = WhereScope::for_alias(
5573 schema_name,
5574 table_name,
5575 WRITTEN_ROW,
5576 context
5577 .type_names
5578 .get(&(schema_name.to_string(), table_name.to_string()))
5579 .map(String::as_str)
5580 .unwrap_or(table_name),
5581 context.cache,
5582 context.relationships,
5583 context.names,
5584 );
5585 let (check_sql, check_values) = {
5586 let check = crate::role::write_check(
5587 &context.caller,
5588 context.names,
5589 schema_name,
5590 table_name,
5591 crate::role::Verb::Insert,
5592 )
5593 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
5594 match check {
5595 None => (String::new(), Vec::new()),
5596 Some(check) => {
5597 let mut values = Vec::new();
5598 let mut param_idx = column_names.len() + conflict_values.len() + 1;
5599 let mut alias_counter = 0usize;
5600 match build_condition(
5601 &check,
5602 &check_scope,
5603 &mut param_idx,
5604 &mut values,
5605 &mut alias_counter,
5606 )? {
5607 None => (String::new(), Vec::new()),
5608 Some(sql) => (
5609 format!(
5610 ", ({}) AS {}",
5611 sql,
5612 postrust_sql::escape_ident(CHECK_COLUMN)
5613 ),
5614 values,
5615 ),
5616 }
5617 }
5618 }
5619 };
5620
5621 let names = column_names;
5622 let written = if names.is_empty() {
5623 let sql = format!(
5626 "INSERT INTO {}.{} AS {} DEFAULT VALUES{} RETURNING {}{}",
5627 postrust_sql::escape_ident(schema_name),
5628 postrust_sql::escape_ident(table_name),
5629 qualified_table,
5630 conflict_sql,
5631 row_json(&qualified_table, &column_types),
5632 check_sql
5633 );
5634 let mut query = sqlx::query(&sql);
5635 for value in &conflict_values {
5636 query = bind_json_value(query, value);
5637 }
5638 for value in &check_values {
5639 query = bind_json_value(query, value);
5640 }
5641 query
5642 .fetch_optional(&mut *conn)
5643 .await
5644 .map_err(|error| at_path(database_error(error), &context.objects))?
5647 } else {
5648 let placeholders: Vec<String> = names
5649 .iter()
5650 .enumerate()
5651 .map(|(i, column)| {
5652 let placeholder = format!("${}", i + 1);
5653 match columns.get(*column) {
5654 Some(value) => write_expression(&column_types, column, value, &placeholder),
5655 None => placeholder,
5656 }
5657 })
5658 .collect();
5659 let sql = format!(
5660 "INSERT INTO {}.{} AS {} ({}) VALUES ({}){} RETURNING {}{}",
5661 postrust_sql::escape_ident(schema_name),
5662 postrust_sql::escape_ident(table_name),
5663 qualified_table,
5664 names
5665 .iter()
5666 .map(|c| postrust_sql::escape_ident(c))
5667 .collect::<Vec<_>>()
5668 .join(", "),
5669 placeholders.join(", "),
5670 conflict_sql,
5671 row_json(&qualified_table, &column_types),
5672 check_sql
5673 );
5674 trace!("Executing INSERT SQL: {}", sql);
5675 let mut query = sqlx::query(&sql);
5676 for column in &names {
5677 if let Some(value) = columns.get(*column) {
5678 query = bind_json_value(query, &write_operand(&column_types, column, value));
5679 }
5680 }
5681 for value in &conflict_values {
5682 query = bind_json_value(query, value);
5683 }
5684 for value in &check_values {
5685 query = bind_json_value(query, value);
5686 }
5687 query
5688 .fetch_optional(&mut *conn)
5689 .await
5690 .map_err(|error| at_path(database_error(error), &context.objects))?
5693 };
5694
5695 let Some(written) = written else {
5698 return Ok(serde_json::Value::Null);
5699 };
5700 refuse_unchecked_rows(std::slice::from_ref(&written), !check_sql.is_empty())
5701 .map_err(|error| at_path(error, &context.objects))?;
5702 let row: serde_json::Value = written.try_get(0).unwrap_or(serde_json::Value::Null);
5703 *written_count += 1;
5704
5705 for (relationship, data, conflict) in to_many {
5707 let plan =
5708 postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, context.cache)
5709 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5710 let children = match data {
5711 serde_json::Value::Array(items) => items,
5712 serde_json::Value::Object(map) => vec![serde_json::Value::Object(map)],
5713 serde_json::Value::Null => Vec::new(),
5714 _ => {
5715 return Err(async_graphql::Error::new(format!(
5716 "\"{}\" takes objects to insert",
5717 relationship.name
5718 )))
5719 }
5720 };
5721 for (child_index, child) in children.into_iter().enumerate() {
5722 let serde_json::Value::Object(mut child) = child else {
5723 continue;
5724 };
5725 for (local, foreign) in &plan.columns {
5726 if child.get(foreign).is_some_and(|v| !v.is_null()) {
5730 return Err(at_path(
5731 validation_error(format!(
5732 "cannot insert \"{}\" columns as their values are already \
5733 being determined by parent insert",
5734 foreign
5735 )),
5736 &format!(
5737 "{}.{}{}",
5738 context.row,
5739 relationship.name,
5740 match relationship.is_list {
5741 true => format!(".data[{}]", child_index),
5742 false => ".data".to_string(),
5743 }
5744 ),
5745 ));
5746 }
5747 if let Some(value) = row.get(local) {
5748 child.insert(foreign.clone(), value.clone());
5749 }
5750 }
5751 let at = match relationship.is_list {
5757 true => format!(".data[{}]", child_index),
5758 false => ".data".to_string(),
5759 };
5760 let under = format!("{}.{}", context.row, relationship.name);
5761 let nested = InsertContext {
5762 on_conflict: conflict.clone(),
5763 cache: context.cache,
5764 relationships: context.relationships,
5765 type_names: context.type_names,
5766 names: context.names,
5767 caller: context.caller,
5768 row: format!("{}{}", under, at),
5769 objects: format!("{}.data", under),
5770 conflict: format!("{}.on_conflict", under),
5771 };
5772 insert_row(
5773 conn,
5774 &plan.foreign_schema,
5775 &plan.foreign_table,
5776 child,
5777 &nested,
5778 written_count,
5779 )
5780 .await?;
5781 }
5782 }
5783
5784 Ok(row)
5785 })
5786}
5787
5788#[derive(Clone)]
5794struct InsertContext<'a> {
5795 on_conflict: Option<serde_json::Value>,
5798 cache: &'a SchemaCache,
5799 relationships: &'a HashMap<String, Vec<RelationshipField>>,
5800 type_names: &'a HashMap<(String, String), String>,
5804 names: &'a crate::names::NameOverrides,
5807 caller: crate::role::Caller<'a>,
5811 row: String,
5816 objects: String,
5820 conflict: String,
5822}
5823
5824#[allow(clippy::too_many_arguments)]
5837async fn reread_returning(
5838 pool: &PgPool,
5839 gql_ctx: &GraphQLContext,
5840 schema_name: &str,
5841 table_name: &str,
5842 type_name: &str,
5843 rows: Vec<Value>,
5844 returning: async_graphql::SelectionField<'_>,
5845 relationships: &HashMap<String, Vec<RelationshipField>>,
5846 names: &crate::names::NameOverrides,
5847 max_rows: Option<i64>,
5848) -> Result<Vec<Value>, async_graphql::Error> {
5849 let guard = gql_ctx
5850 .schema_cache
5851 .get()
5852 .await
5853 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
5854 let Some(cache) = guard.as_ref() else {
5855 return Ok(rows);
5856 };
5857 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
5858 let Some(table) = cache.get_table(&qi) else {
5859 return Ok(rows);
5860 };
5861 if table.pk_cols.is_empty() {
5862 return Ok(rows);
5864 }
5865
5866 let mut param_idx = 1usize;
5867 let mut values: Vec<serde_json::Value> = Vec::new();
5868 let mut alias_counter = 0usize;
5869 let embeds = build_embed_expressions(
5870 &gql_ctx.caller(),
5871 cache,
5872 relationships,
5873 type_name,
5874 "src",
5875 returning.selection_set(),
5876 max_rows,
5877 &mut alias_counter,
5878 &mut param_idx,
5879 &mut values,
5880 names,
5881 )?;
5882 let computed = computed_projections(
5883 table,
5884 returning.selection_set(),
5885 "src",
5886 names,
5887 cache,
5888 &mut param_idx,
5889 &mut values,
5890 )?;
5891 let renames = names.renames_columns(&table.schema, &table.name);
5895 if embeds.is_empty() && computed.is_empty() && !renames {
5896 return Ok(rows);
5897 }
5898
5899 let mut conditions = Vec::with_capacity(rows.len());
5901 for row in &rows {
5902 let mut parts = Vec::with_capacity(table.pk_cols.len());
5903 for column in &table.pk_cols {
5904 let Value::Object(map) = row else {
5905 return Ok(rows);
5906 };
5907 let Some(value) = map.get(&async_graphql::Name::new(column)) else {
5908 return Ok(rows);
5909 };
5910 parts.push(format!(
5911 "{} = ${}",
5912 postrust_sql::escape_ident(column),
5913 param_idx
5914 ));
5915 values.push(value_to_json(value));
5916 param_idx += 1;
5917 }
5918 conditions.push(format!("({})", parts.join(" AND ")));
5919 }
5920 if conditions.is_empty() {
5921 return Ok(rows);
5922 }
5923
5924 let mut projection =
5925 rename_projection(table, "src", names).unwrap_or_else(|| "src.*".to_string());
5926 for expression in &computed {
5927 projection.push_str(", ");
5928 projection.push_str(expression);
5929 }
5930 for (name, expression) in &embeds {
5931 projection.push_str(", ");
5932 projection.push_str(expression);
5933 projection.push_str(" AS ");
5934 projection.push_str(&postrust_sql::escape_ident(name));
5935 }
5936
5937 let sql = format!(
5938 "SELECT {} FROM (SELECT {} FROM (SELECT * FROM {}.{} WHERE {}) AS src) AS pgrst_r",
5939 row_json("pgrst_r", &exposed_column_types(table, names)),
5940 projection,
5941 postrust_sql::escape_ident(schema_name),
5942 postrust_sql::escape_ident(table_name),
5943 conditions.join(" OR ")
5944 );
5945
5946 let mut guard = write_tx(gql_ctx, pool).await?;
5949 let conn = guard.as_mut().expect("write_tx opens one");
5950 let reread = execute_query_on(conn, &sql, &values).await?;
5951 drop(guard);
5952
5953 if reread.is_empty() {
5954 return Ok(rows);
5955 }
5956 Ok(reread.into_iter().map(json_to_value).collect())
5957}
5958
5959async fn execute_insert(
5961 pool: &PgPool,
5962 gql_ctx: &GraphQLContext,
5963 schema_name: &str,
5964 table_name: &str,
5965 objects: serde_json::Value,
5966 context: &InsertContext<'_>,
5967 single: bool,
5971) -> Result<(Vec<Value>, usize), async_graphql::Error> {
5972 trace!("Insert mutation for {}: {:?}", table_name, objects);
5973
5974 let objects_array = match objects {
5976 serde_json::Value::Array(arr) => arr,
5977 serde_json::Value::Object(obj) => vec![serde_json::Value::Object(obj)],
5978 _ => {
5979 return Err(async_graphql::Error::new(
5980 "objects must be an array or object",
5981 ))
5982 }
5983 };
5984
5985 if objects_array.is_empty() {
5986 return Err(async_graphql::Error::new("objects cannot be empty"));
5987 }
5988
5989 let mut guard = write_tx(gql_ctx, pool).await?;
5990 let conn = guard.as_mut().expect("write_tx opens one");
5991
5992 let mut inserted: Vec<Value> = Vec::new();
5993 let mut written = 0usize;
5994
5995 for (index, object) in objects_array.into_iter().enumerate() {
5996 let serde_json::Value::Object(map) = object else {
5997 return Err(async_graphql::Error::new(
5998 "each object to insert is an object",
5999 ));
6000 };
6001 let context = InsertContext {
6004 row: match single {
6005 true => context.objects.clone(),
6006 false => format!("{}[{}]", context.objects, index),
6007 },
6008 ..context.clone()
6009 };
6010 let row = insert_row(conn, schema_name, table_name, map, &context, &mut written).await?;
6011 if !row.is_null() {
6015 inserted.push(json_to_value(row));
6016 }
6017 }
6018
6019 Ok((inserted, written))
6020}
6021
6022fn mutation_result<'a>(rows: Vec<Value>, affected: usize, by_key: bool) -> Option<FieldValue<'a>> {
6034 if by_key {
6035 return rows.into_iter().next().map(FieldValue::value);
6036 }
6037 let mut response = async_graphql::indexmap::IndexMap::new();
6038 response.insert(
6039 async_graphql::Name::new("affected_rows"),
6040 Value::from(affected),
6041 );
6042 response.insert(async_graphql::Name::new("returning"), Value::List(rows));
6043 Some(FieldValue::value(Value::Object(response)))
6044}
6045
6046fn bind_json_value<'q>(
6048 query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
6049 value: &serde_json::Value,
6050) -> sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments> {
6051 match value {
6052 serde_json::Value::Null => query.bind(None::<String>),
6053 serde_json::Value::Bool(b) => query.bind(*b),
6054 serde_json::Value::Number(n) => {
6055 if let Some(i) = n.as_i64() {
6056 query.bind(i)
6057 } else if let Some(f) = n.as_f64() {
6058 query.bind(f)
6059 } else {
6060 query.bind(n.to_string())
6061 }
6062 }
6063 serde_json::Value::String(s) => query.bind(s.clone()),
6064 _ => query.bind(value.to_string()),
6065 }
6066}
6067
6068#[allow(clippy::too_many_arguments)] async fn execute_update(
6071 pool: &PgPool,
6072 gql_ctx: &GraphQLContext,
6073 schema_name: &str,
6074 table_name: &str,
6075 type_name: &str,
6076 operators: Vec<(&'static str, serde_json::Value)>,
6077 column_types: HashMap<String, String>,
6078 where_clause: Option<serde_json::Value>,
6079 relationships: &HashMap<String, Vec<RelationshipField>>,
6080 names: &crate::names::NameOverrides,
6081) -> Result<Vec<Value>, async_graphql::Error> {
6082 use sqlx::Row;
6083
6084 trace!("Update mutation for {}: {:?}", table_name, operators);
6085
6086 let mut set_parts: Vec<String> = Vec::new();
6091 let mut set_values: Vec<serde_json::Value> = Vec::new();
6092 let mut param_idx = 1;
6093 let mut written: HashSet<String> = HashSet::new();
6094
6095 let preset = crate::role::presets(
6105 &gql_ctx.caller(),
6106 names,
6107 schema_name,
6108 table_name,
6109 crate::role::Verb::Update,
6110 )
6111 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6112 let preset_columns: HashSet<&str> = preset.iter().map(|(name, _)| name.as_str()).collect();
6113 for (column, value) in &preset {
6114 let placeholder =
6115 write_expression(&column_types, column, value, &format!("${}", param_idx));
6116 set_parts.push(format!(
6117 "{} = {}",
6118 postrust_sql::escape_ident(column),
6119 placeholder
6120 ));
6121 set_values.push(write_operand(&column_types, column, value));
6122 param_idx += 1;
6123 }
6124
6125 for (operator, payload) in &operators {
6126 let serde_json::Value::Object(map) = payload else {
6127 return Err(async_graphql::Error::new(format!(
6128 "\"{}\" takes an object mapping columns to values",
6129 operator
6130 )));
6131 };
6132 for (field, value) in map {
6133 let column = &names
6134 .column_source(schema_name, table_name, field)
6135 .unwrap_or(field)
6136 .to_string();
6137 if preset_columns.contains(column.as_str()) {
6140 continue;
6141 }
6142 if !written.insert(column.clone()) {
6143 return Err(async_graphql::Error::new(format!(
6144 "\"{}\" is written twice in one update; a column may be \
6145 changed by one operator at a time",
6146 column
6147 )));
6148 }
6149 let quoted = postrust_sql::escape_ident(column);
6150 let placeholder = match *operator {
6159 "_set" | "_inc" => {
6160 write_expression(&column_types, column, value, &format!("${}", param_idx))
6161 }
6162 _ => format!("${}", param_idx),
6163 };
6164 let assignment = match *operator {
6167 "_set" => format!("{} = {}", quoted, placeholder),
6168 "_inc" => format!("{} = {} + {}", quoted, quoted, placeholder),
6169 "_append" => format!("{} = {} || {}::jsonb", quoted, quoted, placeholder),
6170 "_prepend" => format!("{} = {}::jsonb || {}", quoted, placeholder, quoted),
6171 "_delete_key" => format!("{} = {} - {}::text", quoted, quoted, placeholder),
6172 "_delete_elem" => format!("{} = {} - {}::int", quoted, quoted, placeholder),
6173 "_delete_at_path" => {
6179 let Some(steps) = value.as_array() else {
6180 return Err(async_graphql::Error::new(format!(
6181 "\"_delete_at_path\" on \"{}\" takes a list of keys",
6182 column
6183 )));
6184 };
6185 let path = sql_array(steps, "text[]", &mut param_idx, &mut set_values);
6186 set_parts.push(format!("{} = {} #- {}", quoted, quoted, path));
6187 continue;
6188 }
6189 other => {
6190 return Err(async_graphql::Error::new(format!(
6191 "unsupported update operator \"{}\"",
6192 other
6193 )))
6194 }
6195 };
6196 if *operator == "_set"
6197 && matches!(
6198 column_types.get(column).map(String::as_str),
6199 Some("geometry") | Some("geography")
6200 )
6201 {
6202 check_geojson(value)?;
6203 }
6204 set_parts.push(assignment);
6205 set_values.push(match *operator {
6206 "_set" => write_operand(&column_types, column, value),
6207 _ => value.clone(),
6208 });
6209 param_idx += 1;
6210 }
6211 }
6212
6213 if set_parts.is_empty() {
6218 return Ok(Vec::new());
6219 }
6220
6221 let guard = gql_ctx
6225 .schema_cache
6226 .get()
6227 .await
6228 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6229 let cache = guard
6230 .as_ref()
6231 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6232 let scope = WhereScope::table(schema_name, table_name, type_name, names)
6233 .under_alias(WRITTEN_ROW)
6234 .with_resolution(cache, relationships);
6235 let (where_sql, where_values) = build_where_clause(where_clause.as_ref(), param_idx, &scope)?;
6236
6237 if where_sql.is_empty() {
6240 return Err(async_graphql::Error::new(format!(
6241 "update on \"{}\" requires a `where` argument with at least one \
6242 recognised condition; refusing to update every row",
6243 table_name
6244 )));
6245 }
6246
6247 let (check_sql, check_values) = permission_check_sql(
6252 gql_ctx,
6253 names,
6254 schema_name,
6255 table_name,
6256 crate::role::Verb::Update,
6257 &scope,
6258 param_idx + where_values.len(),
6259 )?;
6260
6261 let sql = format!(
6262 "UPDATE {}.{} AS {} SET {} {} RETURNING {}{}",
6263 postrust_sql::escape_ident(schema_name),
6264 postrust_sql::escape_ident(table_name),
6265 postrust_sql::escape_ident(WRITTEN_ROW),
6266 set_parts.join(", "),
6267 where_sql,
6268 row_json(&postrust_sql::escape_ident(WRITTEN_ROW), &column_types),
6269 check_sql
6270 );
6271
6272 trace!("Executing UPDATE SQL: {}", sql);
6273
6274 let mut query = sqlx::query(&sql);
6276
6277 for val in &set_values {
6279 query = bind_json_value(query, val);
6280 }
6281
6282 for val in &where_values {
6284 query = bind_json_value(query, val);
6285 }
6286
6287 for val in &check_values {
6289 query = bind_json_value(query, val);
6290 }
6291
6292 let mut guard = write_tx(gql_ctx, pool).await?;
6293 let conn = guard.as_mut().expect("write_tx opens one");
6294 let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6295
6296 refuse_unchecked_rows(&rows, !check_sql.is_empty())?;
6297
6298 let updated: Vec<Value> = rows
6299 .iter()
6300 .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6301 .map(json_to_value)
6302 .collect();
6303
6304 Ok(updated)
6305}
6306
6307fn permission_check_sql(
6314 gql_ctx: &GraphQLContext,
6315 names: &crate::names::NameOverrides,
6316 schema_name: &str,
6317 table_name: &str,
6318 verb: crate::role::Verb,
6319 scope: &WhereScope<'_>,
6320 start_param_idx: usize,
6321) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6322 let check = crate::role::write_check(&gql_ctx.caller(), names, schema_name, table_name, verb)
6323 .map_err(|fault| coded_error(fault.code(), fault.to_string()))?;
6324 let Some(check) = check else {
6325 return Ok((String::new(), Vec::new()));
6326 };
6327
6328 let mut values = Vec::new();
6329 let mut param_idx = start_param_idx;
6330 let mut alias_counter = 0usize;
6331 let condition = build_condition(
6332 &check,
6333 scope,
6334 &mut param_idx,
6335 &mut values,
6336 &mut alias_counter,
6337 )?;
6338 Ok(match condition {
6339 None => (String::new(), Vec::new()),
6342 Some(sql) => (
6343 format!(
6344 ", ({}) AS {}",
6345 sql,
6346 postrust_sql::escape_ident(CHECK_COLUMN)
6347 ),
6348 values,
6349 ),
6350 })
6351}
6352
6353const CHECK_COLUMN: &str = "pgrst_permission_check";
6355
6356fn refuse_unchecked_rows(
6363 rows: &[sqlx::postgres::PgRow],
6364 checked: bool,
6365) -> Result<(), async_graphql::Error> {
6366 if !checked {
6367 return Ok(());
6368 }
6369 use sqlx::Row;
6370 let all_passed = rows
6371 .iter()
6372 .all(|row| row.try_get::<bool, _>(CHECK_COLUMN).unwrap_or(false));
6373 match all_passed {
6374 true => Ok(()),
6375 false => Err(coded_error("permission-error", crate::role::CHECK_FAILED)),
6376 }
6377}
6378
6379#[allow(clippy::too_many_arguments)] async fn execute_delete(
6382 pool: &PgPool,
6383 gql_ctx: &GraphQLContext,
6384 schema_name: &str,
6385 table_name: &str,
6386 type_name: &str,
6387 where_clause: Option<serde_json::Value>,
6388 returning: Option<async_graphql::SelectionField<'_>>,
6389 relationships: &HashMap<String, Vec<RelationshipField>>,
6390 names: &crate::names::NameOverrides,
6391 max_rows: Option<i64>,
6392) -> Result<Vec<Value>, async_graphql::Error> {
6393 use sqlx::Row;
6394
6395 trace!("Delete mutation for {}", table_name);
6396
6397 let guard = gql_ctx
6398 .schema_cache
6399 .get()
6400 .await
6401 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
6402 let cache = guard
6403 .as_ref()
6404 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
6405 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
6406 let column_types = cache
6407 .get_table(&qi)
6408 .map(|table| exposed_column_types(table, names))
6409 .unwrap_or_default();
6410
6411 let scope = WhereScope::table(schema_name, table_name, type_name, names)
6415 .under_alias(WRITTEN_ROW)
6416 .with_resolution(cache, relationships);
6417 let (where_sql, mut values) = build_where_clause(where_clause.as_ref(), 1, &scope)?;
6418
6419 if where_sql.is_empty() {
6422 return Err(async_graphql::Error::new(format!(
6423 "delete on \"{}\" requires a `where` argument with at least one \
6424 recognised condition; refusing to delete every row",
6425 table_name
6426 )));
6427 }
6428
6429 let mut projection = String::new();
6435 let renamed = cache
6436 .get_table(&qi)
6437 .and_then(|table| rename_projection(table, "src", names));
6438 if let Some(returning) = returning {
6439 let mut param_idx = values.len() + 1;
6440 let mut alias_counter = 0usize;
6441 let embeds = build_embed_expressions(
6442 &gql_ctx.caller(),
6443 cache,
6444 relationships,
6445 type_name,
6446 "src",
6447 returning.selection_set(),
6448 max_rows,
6449 &mut alias_counter,
6450 &mut param_idx,
6451 &mut values,
6452 names,
6453 )?;
6454 let computed = match cache.get_table(&qi) {
6455 Some(table) => computed_projections(
6456 table,
6457 returning.selection_set(),
6458 "src",
6459 names,
6460 cache,
6461 &mut param_idx,
6462 &mut values,
6463 )?,
6464 None => Vec::new(),
6465 };
6466 for expression in &computed {
6467 projection.push_str(", ");
6468 projection.push_str(expression);
6469 }
6470 for (name, expression) in &embeds {
6471 projection.push_str(", ");
6472 projection.push_str(expression);
6473 projection.push_str(" AS ");
6474 projection.push_str(&postrust_sql::escape_ident(name));
6475 }
6476 }
6477
6478 let written = postrust_sql::escape_ident(WRITTEN_ROW);
6479 let sql = if projection.is_empty() && renamed.is_none() {
6480 format!(
6481 "DELETE FROM {}.{} AS {} {} RETURNING {}",
6482 postrust_sql::escape_ident(schema_name),
6483 postrust_sql::escape_ident(table_name),
6484 written,
6485 where_sql,
6486 row_json(&written, &column_types)
6487 )
6488 } else {
6489 format!(
6490 "WITH pgrst_deleted AS (DELETE FROM {}.{} AS {} {} RETURNING {}.*) \
6491 SELECT {} FROM (SELECT {}{} FROM pgrst_deleted AS src) AS pgrst_r",
6492 postrust_sql::escape_ident(schema_name),
6493 postrust_sql::escape_ident(table_name),
6494 written,
6495 where_sql,
6496 written,
6497 row_json("pgrst_r", &column_types),
6498 renamed.unwrap_or_else(|| "src.*".to_string()),
6499 projection
6500 )
6501 };
6502
6503 trace!("Executing DELETE SQL: {}", sql);
6504
6505 let mut query = sqlx::query(&sql);
6506 for val in &values {
6507 query = bind_json_value(query, val);
6508 }
6509
6510 let mut guard = write_tx(gql_ctx, pool).await?;
6511 let conn = guard.as_mut().expect("write_tx opens one");
6512 let rows = query.fetch_all(&mut **conn).await.map_err(database_error)?;
6513
6514 let deleted: Vec<Value> = rows
6515 .iter()
6516 .filter_map(|row| row.try_get::<serde_json::Value, _>(0).ok())
6517 .map(json_to_value)
6518 .collect();
6519
6520 Ok(deleted)
6521}
6522
6523fn permission_predicate(
6531 caller: &crate::role::Caller<'_>,
6532 names: &crate::names::NameOverrides,
6533 schema: &str,
6534 table: &str,
6535) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6536 permission_filter(caller, names, schema, table, crate::role::Verb::Select)
6537}
6538
6539fn permission_filter(
6541 caller: &crate::role::Caller<'_>,
6542 names: &crate::names::NameOverrides,
6543 schema: &str,
6544 table: &str,
6545 verb: crate::role::Verb,
6546) -> Result<Option<serde_json::Value>, async_graphql::Error> {
6547 crate::role::row_filter(caller, names, schema, table, verb)
6548 .map(|filter| filter.map(|predicate| serde_json::json!({ PERMISSION: predicate })))
6549 .map_err(|fault| coded_error(fault.code(), fault.to_string()))
6550}
6551
6552const PERMISSION: &str = "_permission";
6573
6574fn permission_limit(
6580 caller: &crate::role::Caller<'_>,
6581 names: &crate::names::NameOverrides,
6582 schema: &str,
6583 table: &str,
6584) -> Option<i64> {
6585 crate::role::read_limit(caller, names, schema, table)
6586}
6587
6588fn and_predicates(
6595 request: Option<serde_json::Value>,
6596 permission: Option<serde_json::Value>,
6597) -> Option<serde_json::Value> {
6598 match (request, permission) {
6599 (None, None) => None,
6600 (Some(only), None) | (None, Some(only)) => Some(only),
6601 (Some(request), Some(permission)) => {
6602 Some(serde_json::json!({ "_and": [request, permission] }))
6603 }
6604 }
6605}
6606
6607fn build_where_clause(
6617 where_value: Option<&serde_json::Value>,
6618 start_param_idx: usize,
6619 scope: &WhereScope<'_>,
6620) -> Result<(String, Vec<serde_json::Value>), async_graphql::Error> {
6621 let mut values: Vec<serde_json::Value> = Vec::new();
6622 let mut param_idx = start_param_idx;
6623 let mut alias_counter = 0usize;
6624
6625 let condition = match where_value {
6626 Some(value) => build_condition(
6627 value,
6628 scope,
6629 &mut param_idx,
6630 &mut values,
6631 &mut alias_counter,
6632 )?,
6633 None => None,
6634 };
6635
6636 Ok(match condition {
6637 Some(sql) => (format!("WHERE {}", sql), values),
6638 None => (String::new(), values),
6639 })
6640}
6641
6642pub struct WhereScope<'a> {
6654 qualified: postrust_core::api_request::QualifiedIdentifier,
6656 sql_ref: String,
6658 row_ref: String,
6662 type_name: String,
6664 root_ref: String,
6672 root_qualified: postrust_core::api_request::QualifiedIdentifier,
6673 names: &'a crate::names::NameOverrides,
6676 resolution: Option<WhereResolution<'a>>,
6677 caller: Option<crate::role::Caller<'a>>,
6684}
6685
6686struct WhereResolution<'a> {
6688 cache: &'a SchemaCache,
6689 relationships: &'a HashMap<String, Vec<RelationshipField>>,
6690}
6691
6692impl<'a> WhereScope<'a> {
6693 pub fn table(
6695 schema: &str,
6696 table: &str,
6697 type_name: &str,
6698 names: &'a crate::names::NameOverrides,
6699 ) -> Self {
6700 Self {
6701 qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6702 sql_ref: format!(
6703 "{}.{}",
6704 postrust_sql::escape_ident(schema),
6705 postrust_sql::escape_ident(table)
6706 ),
6707 row_ref: postrust_sql::escape_ident(table),
6708 type_name: type_name.to_string(),
6709 root_ref: format!(
6710 "{}.{}",
6711 postrust_sql::escape_ident(schema),
6712 postrust_sql::escape_ident(table)
6713 ),
6714 root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6715 names,
6716 resolution: None,
6717 caller: None,
6718 }
6719 }
6720
6721 fn under_alias(mut self, alias: &str) -> Self {
6729 if self.root_ref == self.sql_ref {
6732 self.root_ref = postrust_sql::escape_ident(alias);
6733 }
6734 self.sql_ref = postrust_sql::escape_ident(alias);
6735 self.row_ref = postrust_sql::escape_ident(alias);
6736 self
6737 }
6738
6739 fn with_resolution(
6741 mut self,
6742 cache: &'a SchemaCache,
6743 relationships: &'a HashMap<String, Vec<RelationshipField>>,
6744 ) -> Self {
6745 self.resolution = Some(WhereResolution {
6746 cache,
6747 relationships,
6748 });
6749 self
6750 }
6751
6752 #[allow(clippy::too_many_arguments)]
6754 fn for_alias(
6755 schema: &str,
6756 table: &str,
6757 alias: &str,
6758 type_name: &str,
6759 cache: &'a SchemaCache,
6760 relationships: &'a HashMap<String, Vec<RelationshipField>>,
6761 names: &'a crate::names::NameOverrides,
6762 ) -> Self {
6763 Self {
6764 qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6765 sql_ref: postrust_sql::escape_ident(alias),
6766 row_ref: postrust_sql::escape_ident(alias),
6767 type_name: type_name.to_string(),
6768 root_ref: postrust_sql::escape_ident(alias),
6769 root_qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6770 names,
6771 resolution: Some(WhereResolution {
6772 cache,
6773 relationships,
6774 }),
6775 caller: None,
6776 }
6777 }
6778
6779 fn aliased(
6786 schema: &str,
6787 table: &str,
6788 alias: &str,
6789 type_name: &str,
6790 from: &WhereScope<'a>,
6791 ) -> Self {
6792 Self {
6793 qualified: postrust_core::api_request::QualifiedIdentifier::new(schema, table),
6794 sql_ref: postrust_sql::escape_ident(alias),
6795 row_ref: postrust_sql::escape_ident(alias),
6796 type_name: type_name.to_string(),
6797 root_ref: from.root_ref.clone(),
6800 root_qualified: from.root_qualified.clone(),
6801 names: from.names,
6802 resolution: from.resolution.as_ref().map(|r| WhereResolution {
6803 cache: r.cache,
6804 relationships: r.relationships,
6805 }),
6806 caller: from.caller,
6810 }
6811 }
6812
6813 pub fn for_caller(mut self, caller: crate::role::Caller<'a>) -> Self {
6815 self.caller = Some(caller);
6816 self
6817 }
6818
6819 fn as_system(&self) -> WhereScope<'a> {
6822 WhereScope {
6823 qualified: self.qualified.clone(),
6824 sql_ref: self.sql_ref.clone(),
6825 row_ref: self.row_ref.clone(),
6826 type_name: self.type_name.clone(),
6827 root_ref: self.sql_ref.clone(),
6834 root_qualified: self.qualified.clone(),
6835 names: self.names,
6836 resolution: self.resolution.as_ref().map(|r| WhereResolution {
6837 cache: r.cache,
6838 relationships: r.relationships,
6839 }),
6840 caller: None,
6841 }
6842 }
6843
6844 fn column(&self, name: &str) -> String {
6852 let column = self.column_name(name);
6853 let plain = format!("{}.{}", self.sql_ref, postrust_sql::escape_ident(column));
6854 let Some(cache) = self.resolution.as_ref().map(|r| r.cache) else {
6855 return plain;
6856 };
6857 let Some(table) = cache.get_table(&self.qualified) else {
6858 return plain;
6859 };
6860 if table.get_column(column).is_some() {
6861 return plain;
6862 }
6863 let function = self
6864 .names
6865 .computed_source(&table.schema, &table.name, name)
6866 .unwrap_or(name);
6867 match table.get_computed_column(function) {
6868 Some(definition) => {
6869 computed_call(definition, &format!("{}.*", self.row_ref), &self.row_ref)
6870 }
6871 None => plain,
6872 }
6873 }
6874
6875 fn column_name<'n>(&'n self, name: &'n str) -> &'n str {
6877 self.names
6878 .column_source(&self.qualified.schema, &self.qualified.name, name)
6879 .unwrap_or(name)
6880 }
6881
6882 fn column_type(&self, name: &str) -> Option<String> {
6886 let cache = self.resolution.as_ref()?.cache;
6887 let table = cache.get_table(&self.qualified)?;
6888 table
6889 .get_column(self.column_name(name))
6890 .map(|c| c.nominal_type.clone())
6891 }
6892
6893 fn other_column(
6907 &self,
6908 operand: &serde_json::Value,
6909 op: &str,
6910 ) -> Result<String, async_graphql::Error> {
6911 use serde_json::Value;
6912
6913 let refused = |what: &str| {
6914 async_graphql::Error::new(format!(
6915 "the \"{}\" comparison takes the name of a column, {}",
6916 op, what
6917 ))
6918 };
6919 let (against, qualified, name) = match operand {
6920 Value::String(name) => (&self.sql_ref, &self.qualified, name.as_str()),
6921 Value::Array(path) => match path.as_slice() {
6922 [Value::String(name)] => (&self.sql_ref, &self.qualified, name.as_str()),
6923 [Value::String(root), Value::String(name)] if root == "$" => {
6924 (&self.root_ref, &self.root_qualified, name.as_str())
6925 }
6926 _ => return Err(refused("or [\"$\", \"a column\"]")),
6927 },
6928 _ => return Err(refused("written as a string")),
6929 };
6930 let column = self
6933 .names
6934 .column_source(&qualified.schema, &qualified.name, name)
6935 .unwrap_or(name);
6936 Ok(format!(
6937 "{}.{}",
6938 against,
6939 postrust_sql::escape_ident(column)
6940 ))
6941 }
6942
6943 fn relationship(&self, name: &str) -> Option<std::borrow::Cow<'a, RelationshipField>> {
6957 let resolution = self.resolution.as_ref()?;
6958 let published = resolution
6959 .relationships
6960 .get(&self.type_name)
6961 .and_then(|fields| fields.iter().find(|r| r.name == name));
6962 if let Some(found) = published {
6963 return Some(std::borrow::Cow::Borrowed(found));
6964 }
6965
6966 let table = resolution.cache.get_table(&self.qualified)?;
6967
6968 if let Some(declared) = self
6974 .names
6975 .declared_relationships(&table.schema, &table.name)
6976 {
6977 if let Some(entry) = declared.iter().find(|entry| entry.name == name) {
6978 let target = entry
6981 .target(&table.schema)
6982 .map(|(_, target)| target)
6983 .unwrap_or_default();
6984 if let Some(field) = crate::schema::mapped_relationship_field(
6985 entry,
6986 table,
6987 resolution.cache,
6988 &table.schema,
6989 &target,
6990 &target,
6991 &target,
6992 ) {
6993 return Some(std::borrow::Cow::Owned(field));
6994 }
6995 if let Some(key) = entry.using.as_deref() {
6999 if let Some(found) = resolution
7000 .cache
7001 .get_relationships(&self.qualified, &table.schema)
7002 .into_iter()
7003 .flatten()
7004 .find(|rel| {
7005 crate::schema::relationship_keys(rel)
7006 .iter()
7007 .any(|found| found == key)
7008 })
7009 {
7010 return Some(std::borrow::Cow::Owned(
7011 crate::schema::relationship::RelationshipField::from_relationship_named(
7012 found,
7013 &found.foreign_table().name,
7014 Some(&entry.name),
7015 ),
7016 ));
7017 }
7018 }
7019 }
7020 }
7021
7022 resolution
7023 .cache
7024 .get_relationships(&self.qualified, &table.schema)?
7025 .iter()
7026 .find_map(|rel| {
7027 let given = crate::schema::relationship_keys(rel)
7028 .iter()
7029 .find_map(|key| self.names.relationship(&table.schema, &table.name, key));
7030 let field = RelationshipField::from_relationship_named(
7034 rel,
7035 &rel.foreign_table().name,
7036 given,
7037 );
7038 (field.name == name).then_some(std::borrow::Cow::Owned(field))
7039 })
7040 }
7041}
7042
7043fn column_comparison(op: &str) -> Option<&'static str> {
7052 Some(match op {
7053 "_ceq" => "=",
7054 "_cne" => "<>",
7055 "_cgt" => ">",
7056 "_cgte" => ">=",
7057 "_clt" => "<",
7058 "_clte" => "<=",
7059 _ => return None,
7060 })
7061}
7062
7063fn build_condition(
7065 value: &serde_json::Value,
7066 scope: &WhereScope<'_>,
7067 param_idx: &mut usize,
7068 values: &mut Vec<serde_json::Value>,
7069 alias_counter: &mut usize,
7070) -> Result<Option<String>, async_graphql::Error> {
7071 let serde_json::Value::Object(map) = value else {
7072 return Ok(None);
7073 };
7074
7075 let mut conditions: Vec<String> = Vec::new();
7076
7077 for (key, val) in map {
7078 match key.as_str() {
7079 "_and" | "_or" => {
7083 let members = val.as_array().ok_or_else(|| {
7084 async_graphql::Error::new(format!("\"{}\" takes a list of expressions", key))
7085 })?;
7086 let mut parts = Vec::new();
7087 for member in members {
7088 if let Some(sql) =
7089 build_condition(member, scope, param_idx, values, alias_counter)?
7090 {
7091 parts.push(sql);
7092 }
7093 }
7094 if parts.is_empty() {
7095 if key == "_or" {
7096 conditions.push("false".to_string());
7097 }
7098 continue;
7099 }
7100 let joiner = if key == "_and" { " AND " } else { " OR " };
7101 conditions.push(format!("({})", parts.join(joiner)));
7102 }
7103 "_not" => {
7104 if let Some(sql) = build_condition(val, scope, param_idx, values, alias_counter)? {
7105 conditions.push(format!("NOT ({})", sql));
7106 }
7107 }
7108 PERMISSION => {
7111 if let Some(sql) =
7112 build_condition(val, &scope.as_system(), param_idx, values, alias_counter)?
7113 {
7114 conditions.push(sql);
7115 }
7116 }
7117 "_exists" => {
7121 conditions.push(table_exists_sql(
7122 val,
7123 scope,
7124 param_idx,
7125 values,
7126 alias_counter,
7127 )?);
7128 }
7129 name if scope.relationship(name).is_some() => {
7135 let relationship = scope.relationship(name).expect("just checked");
7136 conditions.push(exists_sql(
7137 &relationship,
7138 val,
7139 scope,
7140 param_idx,
7141 values,
7142 alias_counter,
7143 )?);
7144 }
7145 name if scope.column_type(name).is_none()
7150 && name
7151 .strip_suffix("_aggregate")
7152 .and_then(|rel| scope.relationship(rel))
7153 .is_some_and(|rel| rel.is_list) =>
7154 {
7155 let relationship = name
7156 .strip_suffix("_aggregate")
7157 .and_then(|rel| scope.relationship(rel))
7158 .expect("just checked");
7159 conditions.push(aggregate_predicate_sql(
7160 &relationship,
7161 val,
7162 scope,
7163 param_idx,
7164 values,
7165 alias_counter,
7166 )?);
7167 }
7168 column => {
7169 let quoted = scope.column(column);
7170 match val {
7171 serde_json::Value::Object(ops) => {
7172 let column_type = scope.column_type(column);
7173 for (op, operand) in ops {
7174 if let Some(other) = column_comparison(op) {
7180 conditions.push(format!(
7181 "{} {} {}",
7182 quoted,
7183 other,
7184 scope.other_column(operand, op)?
7185 ));
7186 continue;
7187 }
7188 conditions.push(comparison_sql(
7189 "ed,
7190 column,
7191 column_type.as_deref(),
7192 op,
7193 operand,
7194 param_idx,
7195 values,
7196 )?);
7197 }
7198 }
7199 other => {
7203 conditions.push(format!("{} = ${}", quoted, param_idx));
7204 values.push(other.clone());
7205 *param_idx += 1;
7206 }
7207 }
7208 }
7209 }
7210 }
7211
7212 Ok(if conditions.is_empty() {
7213 None
7214 } else if conditions.len() == 1 {
7215 conditions.pop()
7216 } else {
7217 Some(conditions.join(" AND "))
7218 })
7219}
7220
7221fn exists_sql(
7228 relationship: &RelationshipField,
7229 child_expression: &serde_json::Value,
7230 scope: &WhereScope<'_>,
7231 param_idx: &mut usize,
7232 values: &mut Vec<serde_json::Value>,
7233 alias_counter: &mut usize,
7234) -> Result<String, async_graphql::Error> {
7235 let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7236 async_graphql::Error::new(format!(
7237 "filtering on the relationship \"{}\" is not available here",
7238 relationship.name
7239 ))
7240 })?;
7241
7242 let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7243 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7244
7245 if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7246 return Err(async_graphql::Error::new(format!(
7247 "filtering on \"{}\" is not supported: it is reached through a junction \
7248 rather than by a key",
7249 relationship.name
7250 )));
7251 }
7252
7253 *alias_counter += 1;
7254 let alias = format!("pgrst_rel_{}", alias_counter);
7255 let child_scope = WhereScope::aliased(
7256 &plan.foreign_schema,
7257 &plan.foreign_table,
7258 &alias,
7259 &relationship.target_type,
7260 scope,
7261 );
7262
7263 let (source, mut correlation) = match &plan.function {
7266 Some(function) => (
7267 format!(
7268 "{}.{}({})",
7269 postrust_sql::escape_ident(&function.schema),
7270 postrust_sql::escape_ident(&function.name),
7271 scope.row_ref
7272 ),
7273 Vec::new(),
7274 ),
7275 None => {
7276 let mut columns = Vec::with_capacity(plan.columns.len());
7277 for (parent_column, child_column) in &plan.columns {
7278 columns.push(format!(
7279 "{} = {}",
7280 scope.column(parent_column),
7281 child_scope.column(child_column)
7282 ));
7283 }
7284 (
7285 format!(
7286 "{}.{}",
7287 postrust_sql::escape_ident(&plan.foreign_schema),
7288 postrust_sql::escape_ident(&plan.foreign_table)
7289 ),
7290 columns,
7291 )
7292 }
7293 };
7294
7295 let child_condition = build_condition(
7296 child_expression,
7297 &child_scope,
7298 param_idx,
7299 values,
7300 alias_counter,
7301 )?;
7302 if let Some(sql) = child_condition {
7303 correlation.push(format!("({})", sql));
7304 }
7305
7306 if let Some(caller) = &child_scope.caller {
7311 let permission = permission_predicate(
7318 caller,
7319 child_scope.names,
7320 &plan.foreign_schema,
7321 &plan.foreign_table,
7322 )?;
7323 if let Some(predicate) = permission {
7324 if let Some(sql) =
7325 build_condition(&predicate, &child_scope, param_idx, values, alias_counter)?
7326 {
7327 correlation.push(format!("({})", sql));
7328 }
7329 }
7330 }
7331 if correlation.is_empty() {
7332 correlation.push("true".to_string());
7333 }
7334
7335 Ok(format!(
7336 "EXISTS (SELECT 1 FROM {} AS {} WHERE {})",
7337 source,
7338 postrust_sql::escape_ident(&alias),
7339 correlation.join(" AND ")
7340 ))
7341}
7342
7343fn table_exists_sql(
7368 spec: &serde_json::Value,
7369 scope: &WhereScope<'_>,
7370 param_idx: &mut usize,
7371 values: &mut Vec<serde_json::Value>,
7372 alias_counter: &mut usize,
7373) -> Result<String, async_graphql::Error> {
7374 let Some((schema, table)) = crate::role::exists_target(spec, &scope.qualified.schema) else {
7375 return Err(async_graphql::Error::new(
7376 "\"_exists\" needs a \"_table\" to look in",
7377 ));
7378 };
7379
7380 let qualified = postrust_core::api_request::QualifiedIdentifier::new(&schema, &table);
7384 if scope
7385 .resolution
7386 .as_ref()
7387 .is_some_and(|r| r.cache.get_table(&qualified).is_none())
7388 {
7389 return Err(async_graphql::Error::new(format!(
7390 "\"_exists\" names a table this server does not have: \"{}.{}\"",
7391 schema, table
7392 )));
7393 }
7394
7395 *alias_counter += 1;
7396 let alias = format!("pgrst_exists_{}", alias_counter);
7397 let type_name = scope
7401 .names
7402 .base_name(&schema, &table)
7403 .unwrap_or(&table)
7404 .to_string();
7405 let mut inner = WhereScope::aliased(&schema, &table, &alias, &type_name, scope);
7406 inner.caller = None;
7409
7410 let condition = match spec.get("_where") {
7411 Some(predicate) => build_condition(predicate, &inner, param_idx, values, alias_counter)?,
7412 None => None,
7413 };
7414
7415 Ok(format!(
7416 "EXISTS (SELECT 1 FROM {}.{} AS {} WHERE {})",
7417 postrust_sql::escape_ident(&schema),
7418 postrust_sql::escape_ident(&table),
7419 postrust_sql::escape_ident(&alias),
7420 condition.unwrap_or_else(|| "true".to_string())
7421 ))
7422}
7423
7424fn aggregate_predicate_sql(
7439 relationship: &RelationshipField,
7440 expression: &serde_json::Value,
7441 scope: &WhereScope<'_>,
7442 param_idx: &mut usize,
7443 values: &mut Vec<serde_json::Value>,
7444 alias_counter: &mut usize,
7445) -> Result<String, async_graphql::Error> {
7446 let serde_json::Value::Object(over) = expression else {
7447 return Ok("true".to_string());
7448 };
7449
7450 let cache = scope.resolution.as_ref().map(|r| r.cache).ok_or_else(|| {
7451 async_graphql::Error::new(format!(
7452 "filtering on the relationship \"{}\" is not available here",
7453 relationship.name
7454 ))
7455 })?;
7456 let plan = postrust_core::embed::EmbedPlan::resolve(&relationship.relationship, cache)
7457 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
7458 if plan.junction.is_some() || (plan.function.is_none() && plan.columns.is_empty()) {
7459 return Err(async_graphql::Error::new(format!(
7460 "aggregating over \"{}\" is not supported: it is reached through a junction \
7461 rather than by a key",
7462 relationship.name
7463 )));
7464 }
7465
7466 let mut conditions: Vec<String> = Vec::new();
7467 for (function, spec) in over {
7468 if spec.is_null() {
7469 continue;
7470 }
7471 let serde_json::Value::Object(spec) = spec else {
7472 continue;
7473 };
7474 let Some(predicate) = spec.get("predicate").filter(|p| !p.is_null()) else {
7475 return Err(validation_error(format!(
7476 "\"{}\" over \"{}\" needs a predicate",
7477 function, relationship.name
7478 )));
7479 };
7480
7481 *alias_counter += 1;
7482 let alias = format!("pgrst_agg_{}", alias_counter);
7483 let child_scope = WhereScope::aliased(
7484 &plan.foreign_schema,
7485 &plan.foreign_table,
7486 &alias,
7487 &relationship.target_type,
7488 scope,
7489 );
7490
7491 let (source, mut correlation) = match &plan.function {
7494 Some(routine) => (
7495 format!(
7496 "{}.{}({})",
7497 postrust_sql::escape_ident(&routine.schema),
7498 postrust_sql::escape_ident(&routine.name),
7499 scope.row_ref
7500 ),
7501 Vec::new(),
7502 ),
7503 None => {
7504 let mut columns = Vec::with_capacity(plan.columns.len());
7505 for (parent_column, child_column) in &plan.columns {
7506 columns.push(format!(
7507 "{} = {}",
7508 scope.column(parent_column),
7509 child_scope.column(child_column)
7510 ));
7511 }
7512 (
7513 format!(
7514 "{}.{}",
7515 postrust_sql::escape_ident(&plan.foreign_schema),
7516 postrust_sql::escape_ident(&plan.foreign_table)
7517 ),
7518 columns,
7519 )
7520 }
7521 };
7522 if let Some(filter) = spec.get("filter").filter(|f| !f.is_null()) {
7525 if let Some(sql) =
7526 build_condition(filter, &child_scope, param_idx, values, alias_counter)?
7527 {
7528 correlation.push(format!("({})", sql));
7529 }
7530 }
7531 if correlation.is_empty() {
7532 correlation.push("true".to_string());
7533 }
7534
7535 let arguments = spec.get("arguments").filter(|a| !a.is_null());
7536 let distinct = matches!(spec.get("distinct"), Some(serde_json::Value::Bool(true)));
7537 let (aggregate, operand_type) = match function.as_str() {
7538 "count" => {
7539 let columns: Vec<String> = match arguments {
7540 Some(serde_json::Value::Array(items)) => items
7541 .iter()
7542 .filter_map(|item| item.as_str())
7543 .map(|column| child_scope.column(column))
7544 .collect(),
7545 Some(serde_json::Value::String(one)) => vec![child_scope.column(one)],
7546 _ => Vec::new(),
7547 };
7548 let counted = match columns.len() {
7549 0 => "*".to_string(),
7550 1 => columns.into_iter().next().unwrap_or_default(),
7551 _ => format!("({})", columns.join(", ")),
7552 };
7553 let counted = match distinct && counted != "*" {
7554 true => format!("DISTINCT {}", counted),
7555 false => counted,
7556 };
7557 (format!("count({})", counted), "integer")
7558 }
7559 "bool_and" | "bool_or" => {
7560 let Some(column) = arguments.and_then(|a| a.as_str()) else {
7561 return Err(validation_error(format!(
7562 "\"{}\" over \"{}\" needs the column to fold",
7563 function, relationship.name
7564 )));
7565 };
7566 (
7567 format!("{}({})", function, child_scope.column(column)),
7568 "boolean",
7569 )
7570 }
7571 other => {
7572 return Err(validation_error(format!(
7573 "\"{}\" is not an aggregate a predicate can be written over",
7574 other
7575 )))
7576 }
7577 };
7578
7579 let subselect = format!(
7580 "(SELECT {} FROM {} AS {} WHERE {})",
7581 aggregate,
7582 source,
7583 postrust_sql::escape_ident(&alias),
7584 correlation.join(" AND ")
7585 );
7586 let serde_json::Value::Object(operators) = predicate else {
7587 return Err(validation_error(format!(
7588 "the predicate on \"{}\" is not a comparison",
7589 function
7590 )));
7591 };
7592 for (operator, operand) in operators {
7593 conditions.push(comparison_sql(
7594 &subselect,
7595 function,
7596 Some(operand_type),
7597 operator,
7598 operand,
7599 param_idx,
7600 values,
7601 )?);
7602 }
7603 }
7604
7605 Ok(match conditions.is_empty() {
7606 true => "true".to_string(),
7607 false => format!("({})", conditions.join(" AND ")),
7608 })
7609}
7610
7611#[allow(clippy::too_many_arguments)]
7619fn postgis_sql(
7620 quoted: &str,
7621 column_type: Option<&str>,
7622 function: &str,
7623 op: &str,
7624 operand: &serde_json::Value,
7625 param_idx: &mut usize,
7626 values: &mut Vec<serde_json::Value>,
7627) -> Result<String, async_graphql::Error> {
7628 let is_geography = column_type == Some("geography");
7629 let mut shape = |value: &serde_json::Value, param_idx: &mut usize| -> String {
7630 let placeholder = format!("${}", param_idx);
7631 *param_idx += 1;
7632 values.push(value.clone());
7633 match is_geography {
7634 true => format!("ST_GeomFromGeoJSON({})::geography", placeholder),
7635 false => format!("ST_GeomFromGeoJSON({})", placeholder),
7636 }
7637 };
7638
7639 match op {
7640 "_st_d_within" | "_st_3d_d_within" => {
7642 let serde_json::Value::Object(spec) = operand else {
7643 return Err(async_graphql::Error::new(format!(
7644 "\"{}\" takes {{distance, from}}",
7645 op
7646 )));
7647 };
7648 let Some(from) = spec.get("from") else {
7649 return Err(async_graphql::Error::new(format!(
7650 "\"{}\" needs a shape to measure from",
7651 op
7652 )));
7653 };
7654 let Some(distance) = spec.get("distance") else {
7655 return Err(async_graphql::Error::new(format!(
7656 "\"{}\" needs a distance",
7657 op
7658 )));
7659 };
7660 let from_sql = shape(from, param_idx);
7661 let distance_sql = format!("${}", param_idx);
7662 *param_idx += 1;
7663 values.push(distance.clone());
7664 let spheroid = match (is_geography, spec.get("use_spheroid")) {
7669 (true, Some(use_spheroid)) if !use_spheroid.is_null() => {
7670 let placeholder = format!("${}", param_idx);
7671 *param_idx += 1;
7672 values.push(use_spheroid.clone());
7673 format!(", {}::boolean", placeholder)
7674 }
7675 _ => String::new(),
7676 };
7677 Ok(format!(
7678 "{}({}, {}, {}::float8{})",
7679 function, quoted, from_sql, distance_sql, spheroid
7680 ))
7681 }
7682 "_st_intersects_rast" => {
7684 let placeholder = format!("${}", param_idx);
7685 *param_idx += 1;
7686 values.push(operand.clone());
7687 Ok(format!("{}({}, {}::raster)", function, quoted, placeholder))
7688 }
7689 "_st_intersects_geom_nband" | "_st_intersects_nband_geom" => {
7691 let serde_json::Value::Object(spec) = operand else {
7692 return Err(async_graphql::Error::new(format!(
7693 "\"{}\" takes an object",
7694 op
7695 )));
7696 };
7697 let Some(geometry) = spec.get("geommin") else {
7698 return Err(async_graphql::Error::new(format!(
7699 "\"{}\" needs a shape",
7700 op
7701 )));
7702 };
7703 let band = spec.get("nband").filter(|v| !v.is_null());
7704 let geometry_sql = shape(geometry, param_idx);
7705 Ok(match band {
7706 None => format!("{}({}, {})", function, quoted, geometry_sql),
7707 Some(band) => {
7708 let placeholder = format!("${}", param_idx);
7709 *param_idx += 1;
7710 values.push(band.clone());
7711 format!(
7715 "{}({}, {}::int, {})",
7716 function, quoted, placeholder, geometry_sql
7717 )
7718 }
7719 })
7720 }
7721 _ => {
7723 let other = shape(operand, param_idx);
7724 Ok(format!("{}({}, {})", function, quoted, other))
7725 }
7726 }
7727}
7728
7729fn sql_array(
7735 items: &[serde_json::Value],
7736 cast: &str,
7737 param_idx: &mut usize,
7738 values: &mut Vec<serde_json::Value>,
7739) -> String {
7740 if items.is_empty() {
7741 return format!("ARRAY[]::{}", cast);
7742 }
7743 let placeholders: Vec<String> = items
7744 .iter()
7745 .map(|item| {
7746 let placeholder = format!("${}", param_idx);
7747 *param_idx += 1;
7748 values.push(item.clone());
7749 placeholder
7750 })
7751 .collect();
7752 format!("ARRAY[{}]::{}", placeholders.join(", "), cast)
7753}
7754
7755#[allow(clippy::too_many_arguments)]
7757fn comparison_sql(
7758 quoted: &str,
7759 column: &str,
7760 column_type: Option<&str>,
7761 op: &str,
7762 operand: &serde_json::Value,
7763 param_idx: &mut usize,
7764 values: &mut Vec<serde_json::Value>,
7765) -> Result<String, async_graphql::Error> {
7766 if op == "_cast" {
7772 let serde_json::Value::Object(casts) = operand else {
7773 return Err(async_graphql::Error::new(
7774 "_cast takes an object naming the type to compare as",
7775 ));
7776 };
7777 let mut conditions = Vec::new();
7778 for (target, comparisons) in casts {
7779 let pg_type = match target.as_str() {
7783 "geometry" | "geography" => target.as_str(),
7784 "String" => "text",
7785 _ => {
7786 return Err(async_graphql::Error::new(format!(
7787 "cannot compare \"{}\" as \"{}\"",
7788 column, target
7789 )))
7790 }
7791 };
7792 let serde_json::Value::Object(ops) = comparisons else {
7793 continue;
7794 };
7795 let cast = format!("{}::{}", quoted, pg_type);
7796 for (nested_op, nested_operand) in ops {
7797 conditions.push(comparison_sql(
7798 &cast,
7799 column,
7800 Some(pg_type),
7801 nested_op,
7802 nested_operand,
7803 param_idx,
7804 values,
7805 )?);
7806 }
7807 }
7808 return Ok(match conditions.len() {
7809 0 => "true".to_string(),
7810 1 => conditions.pop().expect("just counted"),
7811 _ => format!("({})", conditions.join(" AND ")),
7812 });
7813 }
7814
7815 if let Some(function) = crate::input::bool_exp::postgis_function(op) {
7818 return postgis_sql(
7819 quoted,
7820 column_type,
7821 function,
7822 op,
7823 operand,
7824 param_idx,
7825 values,
7826 );
7827 }
7828
7829 if let Some((operator, cast)) = crate::input::bool_exp::ltree_operator(op) {
7833 if cast.ends_with("[]") {
7834 let items = operand.as_array().ok_or_else(|| {
7835 async_graphql::Error::new(format!(
7836 "the \"{}\" comparison on \"{}\" takes a list",
7837 op, column
7838 ))
7839 })?;
7840 return Ok(format!(
7841 "{} {} {}",
7842 quoted,
7843 operator,
7844 sql_array(items, cast, param_idx, values)
7845 ));
7846 }
7847 let placeholder = format!("${}", param_idx);
7848 *param_idx += 1;
7849 values.push(operand.clone());
7850 return Ok(format!("{} {} {}::{}", quoted, operator, placeholder, cast));
7851 }
7852
7853 if let Some(function) = match op {
7858 "_jsonb_path_exists" => Some("jsonb_path_exists"),
7859 "_jsonb_path_match" => Some("jsonb_path_match"),
7860 _ => None,
7861 } {
7862 let placeholder = format!("${}", param_idx);
7863 *param_idx += 1;
7864 values.push(operand.clone());
7865 return Ok(format!(
7866 "{}({}, {}::jsonpath)",
7867 function, quoted, placeholder
7868 ));
7869 }
7870
7871 let binary = match op {
7873 "_eq" => Some("="),
7874 "_neq" => Some("<>"),
7875 "_gt" => Some(">"),
7876 "_gte" => Some(">="),
7877 "_lt" => Some("<"),
7878 "_lte" => Some("<="),
7879 "_like" => Some("LIKE"),
7880 "_nlike" => Some("NOT LIKE"),
7881 "_ilike" => Some("ILIKE"),
7882 "_nilike" => Some("NOT ILIKE"),
7883 "_similar" => Some("SIMILAR TO"),
7884 "_nsimilar" => Some("NOT SIMILAR TO"),
7885 "_regex" => Some("~"),
7886 "_nregex" => Some("!~"),
7887 "_iregex" => Some("~*"),
7888 "_niregex" => Some("!~*"),
7889 "_contains" => Some("@>"),
7890 "_contained_in" => Some("<@"),
7891 "_has_key" => Some("?"),
7892 _ => None,
7893 };
7894
7895 if let Some(sql_op) = binary {
7896 let placeholder = format!("${}", param_idx);
7897 *param_idx += 1;
7898 values.push(match op {
7903 "_contains" | "_contained_in" => match operand {
7904 serde_json::Value::String(_)
7905 | serde_json::Value::Number(_)
7906 | serde_json::Value::Bool(_) => serde_json::Value::String(operand.to_string()),
7907 other => other.clone(),
7908 },
7909 _ => operand.clone(),
7910 });
7911 let cast = match op {
7923 "_contains" | "_contained_in" => "::jsonb".to_string(),
7924 "_has_key" => "::text".to_string(),
7925 _ if operand.is_null() => match column_type {
7934 Some(pg_type) => format!("::{}", pg_type),
7935 None => String::new(),
7936 },
7937 _ if operand.is_string() => match column_type {
7948 Some(pg_type) => format!("::{}", pg_type),
7949 None => String::new(),
7950 },
7951 _ => String::new(),
7952 };
7953 return Ok(format!("{} {} {}{}", quoted, sql_op, placeholder, cast));
7954 }
7955
7956 match op {
7957 "_is_null" => Ok(if operand.as_bool().unwrap_or(false) {
7958 format!("{} IS NULL", quoted)
7959 } else {
7960 format!("{} IS NOT NULL", quoted)
7961 }),
7962 "_in" | "_nin" => {
7963 let items = operand.as_array().ok_or_else(|| {
7964 async_graphql::Error::new(format!(
7965 "the \"{}\" comparison on \"{}\" requires a list of values",
7966 op, column
7967 ))
7968 })?;
7969 if items.is_empty() {
7970 return Ok(if op == "_in" { "false" } else { "true" }.to_string());
7973 }
7974 let mut placeholders = Vec::with_capacity(items.len());
7975 for item in items {
7976 let cast = match (item.is_null() || item.is_string(), column_type) {
7979 (true, Some(pg_type)) => format!("::{}", pg_type),
7980 _ => String::new(),
7981 };
7982 placeholders.push(format!("${}{}", param_idx, cast));
7983 values.push(item.clone());
7984 *param_idx += 1;
7985 }
7986 Ok(format!(
7987 "{} {} ({})",
7988 quoted,
7989 if op == "_in" { "IN" } else { "NOT IN" },
7990 placeholders.join(", ")
7991 ))
7992 }
7993 "_has_keys_any" | "_has_keys_all" => {
7994 let items = operand.as_array().ok_or_else(|| {
7995 async_graphql::Error::new(format!(
7996 "the \"{}\" comparison on \"{}\" requires a list of keys",
7997 op, column
7998 ))
7999 })?;
8000 Ok(format!(
8001 "{} {} {}",
8002 quoted,
8003 if op == "_has_keys_any" { "?|" } else { "?&" },
8004 sql_array(items, "text[]", param_idx, values)
8005 ))
8006 }
8007 other => Err(async_graphql::Error::new(format!(
8008 "unsupported comparison \"{}\" on \"{}\"",
8009 other, column
8010 ))),
8011 }
8012}
8013
8014#[allow(clippy::too_many_arguments)] async fn build_order_by_clause(
8024 ctx: &ResolverContext<'_>,
8025 schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
8026 schema_name: &str,
8027 table_name: &str,
8028 type_name: &str,
8029 relationships: &HashMap<String, Vec<RelationshipField>>,
8030 reference: &str,
8031 names: &crate::names::NameOverrides,
8032) -> Result<String, async_graphql::Error> {
8033 let Ok(order_arg) = ctx.args.try_get("order_by") else {
8034 return Ok(String::new());
8035 };
8036
8037 let value = accessor_to_json(&order_arg);
8038 let entries: Vec<&serde_json::Value> = match &value {
8041 serde_json::Value::Array(items) => items.iter().collect(),
8042 serde_json::Value::Object(_) => vec![&value],
8043 serde_json::Value::Null => return Ok(String::new()),
8044 _ => {
8045 return Err(async_graphql::Error::new(
8046 "order_by takes an object or a list of them",
8047 ))
8048 }
8049 };
8050 if entries.is_empty() {
8051 return Ok(String::new());
8052 }
8053
8054 let guard = schema_cache
8055 .get()
8056 .await
8057 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8058 let cache = guard
8059 .as_ref()
8060 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8061 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8062 let table = cache
8063 .get_table(&qi)
8064 .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8065
8066 let mut terms = Vec::new();
8067 let mut alias_counter = 0usize;
8068 for entry in entries {
8069 order_terms_into(
8070 entry,
8071 cache,
8072 relationships,
8073 type_name,
8074 table,
8075 reference,
8076 names,
8077 &mut alias_counter,
8078 &mut terms,
8079 )?;
8080 }
8081
8082 if terms.is_empty() {
8083 return Ok(String::new());
8084 }
8085 Ok(format!(" ORDER BY {}", terms.join(", ")))
8086}
8087
8088#[allow(clippy::too_many_arguments)]
8096fn order_terms_into(
8097 entry: &serde_json::Value,
8098 cache: &SchemaCache,
8099 relationships: &HashMap<String, Vec<RelationshipField>>,
8100 type_name: &str,
8101 table: &postrust_core::schema_cache::Table,
8102 reference: &str,
8103 names: &crate::names::NameOverrides,
8104 alias_counter: &mut usize,
8105 terms: &mut Vec<String>,
8106) -> Result<(), async_graphql::Error> {
8107 let serde_json::Value::Object(map) = entry else {
8108 return Err(async_graphql::Error::new(
8109 "each order_by entry is an object mapping a column to a direction",
8110 ));
8111 };
8112 let available: &[RelationshipField] = relationships
8113 .get(type_name)
8114 .map(|r| r.as_slice())
8115 .unwrap_or(&[]);
8116
8117 for (key, value) in map {
8118 if value.is_null() {
8124 continue;
8125 }
8126
8127 if let Some(name) = value.as_str() {
8129 let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8130 async_graphql::Error::new(format!(
8131 "\"{}\" is not a sort direction; expected one of asc, desc, \
8132 asc_nulls_first, asc_nulls_last, desc_nulls_first, desc_nulls_last",
8133 name
8134 ))
8135 })?;
8136 let column = table_column_for(names, table, key);
8137 if table.get_column(column).is_some() {
8138 terms.push(format!(
8139 "{}.{} {}",
8140 reference,
8141 postrust_sql::escape_ident(column),
8142 sql
8143 ));
8144 continue;
8145 }
8146 let function = names
8149 .computed_source(&table.schema, &table.name, key)
8150 .unwrap_or(key);
8151 if let Some(definition) = table.get_computed_column(function) {
8152 terms.push(format!(
8153 "{} {}",
8154 computed_call(definition, &format!("{}.*", reference), reference),
8155 sql
8156 ));
8157 continue;
8158 }
8159 return Err(async_graphql::Error::new(format!(
8160 "cannot order by unknown column \"{}\" on \"{}\"",
8161 key, table.name
8162 )));
8163 }
8164
8165 if let Some(rel) = key
8167 .strip_suffix("_aggregate")
8168 .and_then(|name| available.iter().find(|r| r.name == name && r.is_list))
8169 {
8170 aggregate_order_terms(value, rel, cache, reference, alias_counter, terms)?;
8171 continue;
8172 }
8173
8174 if let Some(rel) = available.iter().find(|r| r.name == *key && !r.is_list) {
8176 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8177 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8178 if plan.columns.is_empty() {
8179 return Err(async_graphql::Error::new(format!(
8180 "cannot order by \"{}\": it is not reached by a key",
8181 key
8182 )));
8183 }
8184 *alias_counter += 1;
8185 let alias = format!("pgrst_ord_{}", alias_counter);
8186 let quoted_alias = postrust_sql::escape_ident(&alias);
8187 let correlation = plan
8188 .columns
8189 .iter()
8190 .map(|(local, foreign)| {
8191 format!(
8192 "{} = {}.{}",
8193 format_args!("{}.{}", reference, postrust_sql::escape_ident(local)),
8194 quoted_alias,
8195 postrust_sql::escape_ident(foreign)
8196 )
8197 })
8198 .collect::<Vec<_>>()
8199 .join(" AND ");
8200
8201 let target_qi = postrust_core::api_request::QualifiedIdentifier::new(
8202 &plan.foreign_schema,
8203 &plan.foreign_table,
8204 );
8205 let Some(target) = cache.get_table(&target_qi) else {
8206 return Err(async_graphql::Error::new(format!(
8207 "unknown table \"{}\"",
8208 plan.foreign_table
8209 )));
8210 };
8211
8212 let mut nested = Vec::new();
8215 order_terms_into(
8216 value,
8217 cache,
8218 relationships,
8219 &rel.target_type,
8220 target,
8221 "ed_alias,
8222 names,
8223 alias_counter,
8224 &mut nested,
8225 )?;
8226 for term in nested {
8227 let (expression, direction) = term
8228 .rsplit_once(' ')
8229 .map(|(e, d)| (e.to_string(), d.to_string()))
8230 .unwrap_or((term.clone(), String::new()));
8231 terms.push(format!(
8232 "(SELECT {} FROM {}.{} AS {} WHERE {}) {}",
8233 expression,
8234 postrust_sql::escape_ident(&plan.foreign_schema),
8235 postrust_sql::escape_ident(&plan.foreign_table),
8236 quoted_alias,
8237 correlation,
8238 direction
8239 ));
8240 }
8241 continue;
8242 }
8243
8244 return Err(async_graphql::Error::new(format!(
8245 "cannot order by \"{}\" on \"{}\"",
8246 key, table.name
8247 )));
8248 }
8249 Ok(())
8250}
8251
8252fn aggregate_order_terms(
8254 value: &serde_json::Value,
8255 rel: &RelationshipField,
8256 cache: &SchemaCache,
8257 reference: &str,
8258 alias_counter: &mut usize,
8259 terms: &mut Vec<String>,
8260) -> Result<(), async_graphql::Error> {
8261 let serde_json::Value::Object(spec) = value else {
8262 return Err(async_graphql::Error::new(
8263 "ordering by an aggregate takes an object, such as {count: desc}",
8264 ));
8265 };
8266 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, cache)
8267 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8268 if plan.columns.is_empty() && plan.function.is_none() {
8269 return Err(async_graphql::Error::new(format!(
8270 "cannot order by an aggregate of \"{}\": it is not reached by a key",
8271 rel.name
8272 )));
8273 }
8274 if plan.function.is_some() && !rel.arguments.is_empty() {
8277 return Err(async_graphql::Error::new(format!(
8278 "cannot order by an aggregate of \"{}\": it takes arguments, and an \
8279 ordering has nowhere to write them",
8280 rel.name
8281 )));
8282 }
8283
8284 *alias_counter += 1;
8285 let alias = postrust_sql::escape_ident(&format!("pgrst_ord_{}", alias_counter));
8286 let (source, correlation) = match &plan.function {
8289 Some(function) => {
8290 let session = plan.row_argument.as_ref().and_then(|row_argument| {
8291 computed_session_argument(cache, function, row_argument)
8292 .map(|session| (row_argument.clone(), session))
8293 });
8294 let call = match session {
8295 Some((row_argument, session)) => format!(
8296 "{} => {}, {} => {}",
8297 postrust_sql::escape_ident(&row_argument),
8298 reference,
8299 postrust_sql::escape_ident(&session),
8300 SESSION_ARGUMENT
8301 ),
8302 None => reference.to_string(),
8303 };
8304 (
8305 format!(
8306 "{}.{}({})",
8307 postrust_sql::escape_ident(&function.schema),
8308 postrust_sql::escape_ident(&function.name),
8309 call
8310 ),
8311 "true".to_string(),
8312 )
8313 }
8314 None => (
8315 format!(
8316 "{}.{}",
8317 postrust_sql::escape_ident(&plan.foreign_schema),
8318 postrust_sql::escape_ident(&plan.foreign_table)
8319 ),
8320 plan.columns
8321 .iter()
8322 .map(|(local, foreign)| {
8323 format!(
8324 "{}.{} = {}.{}",
8325 reference,
8326 postrust_sql::escape_ident(local),
8327 alias,
8328 postrust_sql::escape_ident(foreign)
8329 )
8330 })
8331 .collect::<Vec<_>>()
8332 .join(" AND "),
8333 ),
8334 };
8335
8336 let mut wanted: Vec<(String, String)> = Vec::new();
8338 for (function, argument) in spec {
8339 if argument.is_null() {
8342 continue;
8343 }
8344 if function == "count" {
8345 let name = argument.as_str().unwrap_or_default();
8346 let Some(direction) = crate::input::order_by::direction_sql(name) else {
8347 return Err(async_graphql::Error::new(format!(
8348 "\"{}\" is not a sort direction",
8349 name
8350 )));
8351 };
8352 wanted.push(("count(*)".to_string(), direction.to_string()));
8353 continue;
8354 }
8355 let serde_json::Value::Object(columns) = argument else {
8356 continue;
8357 };
8358 for (column, direction) in columns {
8359 if direction.is_null() {
8360 continue;
8361 }
8362 let name = direction.as_str().unwrap_or_default();
8363 let Some(direction) = crate::input::order_by::direction_sql(name) else {
8364 return Err(async_graphql::Error::new(format!(
8365 "\"{}\" is not a sort direction",
8366 name
8367 )));
8368 };
8369 wanted.push((
8372 format!(
8373 "{}({}.{})",
8374 function,
8375 alias,
8376 postrust_sql::escape_ident(column)
8377 ),
8378 direction.to_string(),
8379 ));
8380 }
8381 }
8382
8383 for (expression, direction) in wanted {
8384 terms.push(format!(
8385 "(SELECT {} FROM {} AS {} WHERE {}) {}",
8386 expression, source, alias, correlation, direction
8387 ));
8388 }
8389 Ok(())
8390}
8391
8392fn distinct_on_clause(
8406 distinct_on: &[String],
8407 written: Option<&str>,
8408) -> Result<(String, Option<String>), async_graphql::Error> {
8409 if distinct_on.is_empty() {
8410 return Ok((String::new(), written.map(str::to_string)));
8411 }
8412 let terms: Vec<&str> = written
8413 .map(|written| written.split(", ").collect())
8414 .unwrap_or_default();
8415 let column_of = |term: &str| {
8418 let expression = term.split_whitespace().next().unwrap_or(term);
8419 expression
8420 .rsplit_once('.')
8421 .map(|(_, name)| name)
8422 .unwrap_or(expression)
8423 .to_string()
8424 };
8425 let leading: Vec<String> = terms
8426 .iter()
8427 .take(distinct_on.len())
8428 .map(|term| column_of(term))
8429 .collect();
8430 if !terms.is_empty()
8431 && (leading.len() != distinct_on.len()
8432 || distinct_on
8433 .iter()
8434 .any(|column| !leading.contains(&column_of(column))))
8435 {
8436 return Err(validation_error(
8437 "\"distinct_on\" columns must match initial \"order_by\" columns",
8438 ));
8439 }
8440 let order = match terms.is_empty() {
8441 true => Some(distinct_on.join(", ")),
8442 false => written.map(str::to_string),
8443 };
8444 Ok((format!("DISTINCT ON ({}) ", distinct_on.join(", ")), order))
8445}
8446
8447async fn build_distinct_on(
8455 ctx: &ResolverContext<'_>,
8456 schema_cache: &postrust_core::schema_cache::SchemaCacheRef,
8457 schema_name: &str,
8458 table_name: &str,
8459 names: &crate::names::NameOverrides,
8460) -> Result<Vec<String>, async_graphql::Error> {
8461 let Ok(arg) = ctx.args.try_get("distinct_on") else {
8462 return Ok(Vec::new());
8463 };
8464 let value = accessor_to_json(&arg);
8465 let requested: Vec<String> = match &value {
8466 serde_json::Value::Array(items) => items
8467 .iter()
8468 .filter_map(|i| i.as_str().map(|s| s.to_string()))
8469 .collect(),
8470 serde_json::Value::String(one) => vec![one.clone()],
8471 _ => Vec::new(),
8472 };
8473 if requested.is_empty() {
8474 return Ok(Vec::new());
8475 }
8476
8477 let guard = schema_cache
8478 .get()
8479 .await
8480 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
8481 let cache = guard
8482 .as_ref()
8483 .ok_or_else(|| async_graphql::Error::new("schema cache is not loaded"))?;
8484 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8485 let table = cache
8486 .get_table(&qi)
8487 .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8488
8489 let mut quoted = Vec::with_capacity(requested.len());
8490 for field in requested {
8491 let name = table_column_for(names, table, &field).to_string();
8492 if table.get_column(&name).is_none() {
8493 return Err(async_graphql::Error::new(format!(
8494 "cannot take distinct on unknown column \"{}\" of \"{}\"",
8495 field, table_name
8496 )));
8497 }
8498 quoted.push(postrust_sql::escape_ident(&name));
8499 }
8500 Ok(quoted)
8501}
8502
8503#[allow(clippy::too_many_arguments)] fn computed_projections<'a>(
8517 table: &postrust_core::schema_cache::Table,
8518 fields: impl IntoIterator<Item = SelectionField<'a>>,
8519 row_reference: &str,
8520 names: &crate::names::NameOverrides,
8521 schema_cache: &SchemaCache,
8522 param_idx: &mut usize,
8523 values: &mut Vec<serde_json::Value>,
8524) -> Result<Vec<String>, async_graphql::Error> {
8525 let mut projections = Vec::new();
8526 for field in fields {
8527 let name = field.name();
8528 if table
8531 .get_column(table_column_for(names, table, name))
8532 .is_some()
8533 {
8534 continue;
8535 }
8536 let function = names
8539 .computed_source(&table.schema, &table.name, name)
8540 .unwrap_or(name);
8541 let Some(computed) = table.get_computed_column(function) else {
8542 continue;
8543 };
8544 let arguments = crate::schema::computed_caller_arguments(computed, schema_cache);
8545 let given = match arguments.is_empty() {
8546 true => None,
8547 false => embed_arguments(field).remove("args"),
8548 };
8549 projections.push(format!(
8550 "{} AS {}",
8551 computed_column_call(
8552 computed,
8553 row_reference,
8554 name,
8555 &arguments,
8556 given.as_ref(),
8557 param_idx,
8558 values,
8559 )?,
8560 postrust_sql::escape_ident(name)
8561 ));
8562 }
8563 Ok(projections)
8564}
8565
8566fn computed_column_call(
8575 computed: &postrust_core::schema_cache::ComputedColumn,
8576 row: &str,
8577 field_name: &str,
8578 arguments: &[(String, String, bool)],
8579 given: Option<&serde_json::Value>,
8580 param_idx: &mut usize,
8581 values: &mut Vec<serde_json::Value>,
8582) -> Result<String, async_graphql::Error> {
8583 if arguments.is_empty() {
8584 return Ok(computed_call(computed, row, row));
8585 }
8586 let Some(row_argument) = &computed.row_argument else {
8587 return Ok(computed_call(computed, row, row));
8588 };
8589 let function = format!(
8590 "{}.{}",
8591 postrust_sql::escape_ident(&computed.function.schema),
8592 postrust_sql::escape_ident(&computed.function.name)
8593 );
8594 let mut passed = vec![format!(
8595 "{} => {}",
8596 postrust_sql::escape_ident(row_argument),
8597 row
8598 )];
8599 if let Some(session) = &computed.session_argument {
8600 passed.push(format!(
8601 "{} => {}",
8602 postrust_sql::escape_ident(session),
8603 SESSION_ARGUMENT
8604 ));
8605 }
8606 for (name, pg_type, required) in arguments {
8607 match given.and_then(|given| given.get(name)) {
8608 None if *required => {
8609 return Err(async_graphql::Error::new(format!(
8610 "{} needs the argument \"{}\"",
8611 field_name, name
8612 )))
8613 }
8614 None => continue,
8616 Some(value) => {
8617 passed.push(format!(
8618 "{} => ${}::{}",
8619 postrust_sql::escape_ident(name),
8620 param_idx,
8621 pg_type
8622 ));
8623 *param_idx += 1;
8624 values.push(value.clone());
8625 }
8626 }
8627 }
8628 Ok(format!("{}({})", function, passed.join(", ")))
8629}
8630
8631fn count_expression(
8643 field: async_graphql::SelectionField<'_>,
8644 table: (&str, &str),
8645 alias: Option<&str>,
8646 names: &crate::names::NameOverrides,
8647) -> String {
8648 let arguments = embed_arguments(field);
8649 let requested = match arguments.get("columns") {
8650 Some(serde_json::Value::Array(items)) => items
8651 .iter()
8652 .filter_map(|item| item.as_str())
8653 .collect::<Vec<_>>(),
8654 Some(serde_json::Value::String(one)) => vec![one.as_str()],
8655 _ => Vec::new(),
8656 };
8657 if requested.is_empty() {
8658 return "count(*)".to_string();
8659 }
8660 let columns: Vec<String> = requested
8661 .into_iter()
8662 .map(|column| {
8663 let (schema, name) = table;
8664 let source = column_for(names, schema, name, column);
8665 match alias {
8666 Some(alias) => format!(
8667 "{}.{}",
8668 postrust_sql::escape_ident(alias),
8669 postrust_sql::escape_ident(source)
8670 ),
8671 None => postrust_sql::escape_ident(source),
8672 }
8673 })
8674 .collect();
8675 let counted = match columns.len() {
8678 1 => columns.into_iter().next().unwrap_or_default(),
8679 _ => format!("({})", columns.join(", ")),
8680 };
8681 let distinct = matches!(
8682 arguments.get("distinct"),
8683 Some(serde_json::Value::Bool(true))
8684 );
8685 match distinct {
8686 true => format!("count(DISTINCT {})", counted),
8687 false => format!("count({})", counted),
8688 }
8689}
8690
8691#[allow(clippy::too_many_arguments)] fn nested_aggregate_select(
8699 caller: &crate::role::Caller<'_>,
8700 selection: async_graphql::SelectionField<'_>,
8701 child_alias: &str,
8702 schema_cache: &SchemaCache,
8703 relationships: &HashMap<String, Vec<RelationshipField>>,
8704 target_type: &str,
8705 child_table: Option<&postrust_core::schema_cache::Table>,
8706 max_rows: Option<i64>,
8707 alias_counter: &mut usize,
8708 param_idx: &mut usize,
8709 values: &mut Vec<serde_json::Value>,
8710 names: &crate::names::NameOverrides,
8711) -> Result<String, async_graphql::Error> {
8712 use crate::schema::aggregate as agg;
8713
8714 let mut parts: Vec<String> = Vec::new();
8715
8716 for child in selection.selection_set() {
8717 match child.name() {
8718 "aggregate" => {
8719 let of_table = child_table
8720 .map(|table| (table.schema.as_str(), table.name.as_str()))
8721 .unwrap_or(("", ""));
8722 let mut counts: Vec<(String, String)> = Vec::new();
8727 let mut wanted: BTreeMap<&str, Vec<String>> = BTreeMap::new();
8728 for function in child.selection_set() {
8729 if function.name() == "count" {
8730 counts.push((
8731 function.alias().unwrap_or("count").to_string(),
8732 count_expression(function, of_table, Some(child_alias), names),
8733 ));
8734 continue;
8735 }
8736 let sql_function = agg::NUMERIC_AGGREGATES
8737 .iter()
8738 .chain(agg::ORDERED_AGGREGATES.iter())
8739 .find(|(name, _)| *name == function.name())
8740 .map(|(name, _)| *name);
8741 let Some(sql_function) = sql_function else {
8742 continue;
8743 };
8744 let columns = wanted.entry(sql_function).or_default();
8745 for column in function.selection_set() {
8746 let name = column.name().to_string();
8747 if name != "__typename" && !columns.contains(&name) {
8749 columns.push(name);
8750 }
8751 }
8752 }
8753 let mut fields = match counts.iter().any(|(key, _)| key == "count") {
8756 true => Vec::new(),
8757 false => vec!["'count', count(*)".to_string()],
8758 };
8759 for (key, expression) in &counts {
8760 fields.push(format!(
8761 "{}, {}",
8762 postrust_sql::quote_literal(key),
8763 expression
8764 ));
8765 }
8766 for (sql_function, named) in &wanted {
8767 let columns: Vec<String> = named
8768 .iter()
8769 .map(|column| {
8770 let source = child_table
8771 .map(|table| table_column_for(names, table, column))
8772 .unwrap_or(column.as_str());
8773 format!(
8774 "{}, {}({}.{})",
8775 postrust_sql::quote_literal(column),
8776 sql_function,
8777 postrust_sql::escape_ident(child_alias),
8778 postrust_sql::escape_ident(source)
8779 )
8780 })
8781 .collect();
8782 fields.push(format!(
8785 "'{}', json_build_object({})",
8786 sql_function,
8787 columns.join(", ")
8788 ));
8789 }
8790 parts.push(format!(
8791 "json_build_object({}) AS {}",
8792 fields.join(", "),
8793 postrust_sql::escape_ident("aggregate")
8794 ));
8795 }
8796 "nodes" => {
8801 let embeds = build_embed_expressions(
8802 caller,
8803 schema_cache,
8804 relationships,
8805 target_type,
8806 child_alias,
8807 child.selection_set(),
8808 max_rows,
8809 alias_counter,
8810 param_idx,
8811 values,
8812 names,
8813 )?;
8814 let mut columns: Vec<String> = Vec::new();
8815 for column in child.selection_set() {
8816 let name = column.name();
8817 if name == "__typename" || embeds.iter().any(|(field, _)| field == name) {
8818 continue;
8819 }
8820 columns.push(format!(
8821 "{}, {}",
8822 postrust_sql::quote_literal(name),
8823 column_expression(child_table, child_alias, name, names)
8824 ));
8825 }
8826 for (field, expression) in &embeds {
8827 columns.push(format!(
8828 "{}, {}",
8829 postrust_sql::quote_literal(field),
8830 expression
8831 ));
8832 }
8833 let row = if columns.is_empty() {
8834 format!("row_to_json({})", postrust_sql::escape_ident(child_alias))
8835 } else {
8836 format!("json_build_object({})", columns.join(", "))
8837 };
8838 parts.push(format!(
8839 "COALESCE(json_agg({}), '[]'::json) AS {}",
8840 row,
8841 postrust_sql::escape_ident("nodes")
8842 ));
8843 }
8844 _ => {}
8845 }
8846 }
8847
8848 if parts.is_empty() {
8849 parts.push("count(*) AS pgrst_empty".to_string());
8852 }
8853 Ok(parts.join(", "))
8854}
8855
8856fn column_expression(
8861 table: Option<&postrust_core::schema_cache::Table>,
8862 alias: &str,
8863 name: &str,
8864 names: &crate::names::NameOverrides,
8865) -> String {
8866 let column = table
8867 .map(|t| table_column_for(names, t, name))
8868 .unwrap_or(name);
8869 let plain = format!(
8870 "{}.{}",
8871 postrust_sql::escape_ident(alias),
8872 postrust_sql::escape_ident(column)
8873 );
8874 let computed = table
8875 .filter(|t| t.get_column(column).is_none())
8876 .and_then(|t| {
8877 let function = names
8878 .computed_source(&t.schema, &t.name, name)
8879 .unwrap_or(name);
8880 t.get_computed_column(function)
8881 });
8882 match computed {
8883 Some(definition) => computed_call(
8884 definition,
8885 &format!("{}.*", postrust_sql::escape_ident(alias)),
8886 &postrust_sql::escape_ident(alias),
8887 ),
8888 None => plain,
8889 }
8890}
8891
8892fn order_terms(
8900 order: &serde_json::Value,
8901 schema_cache: &SchemaCache,
8902 schema_name: &str,
8903 table_name: &str,
8904 alias: &str,
8905 names: &crate::names::NameOverrides,
8906) -> Result<Option<String>, async_graphql::Error> {
8907 let entries: Vec<&serde_json::Value> = match order {
8908 serde_json::Value::Array(items) => items.iter().collect(),
8909 serde_json::Value::Object(_) => vec![order],
8910 _ => return Ok(None),
8911 };
8912
8913 let qi = postrust_core::api_request::QualifiedIdentifier::new(schema_name, table_name);
8914 let table = schema_cache
8915 .get_table(&qi)
8916 .ok_or_else(|| async_graphql::Error::new(format!("unknown table \"{}\"", table_name)))?;
8917
8918 let mut terms = Vec::new();
8919 for entry in entries {
8920 let serde_json::Value::Object(map) = entry else {
8921 continue;
8922 };
8923 for (field, direction) in map {
8924 if direction.is_null() {
8926 continue;
8927 }
8928 let column = table_column_for(names, table, field);
8929 if table.get_column(column).is_none() {
8930 return Err(async_graphql::Error::new(format!(
8931 "cannot order by unknown column \"{}\" on \"{}\"",
8932 field, table_name
8933 )));
8934 }
8935 let name = direction.as_str().unwrap_or_default();
8936 let sql = crate::input::order_by::direction_sql(name).ok_or_else(|| {
8937 async_graphql::Error::new(format!("\"{}\" is not a sort direction", name))
8938 })?;
8939 terms.push(format!(
8940 "{}.{} {}",
8941 postrust_sql::escape_ident(alias),
8942 postrust_sql::escape_ident(column),
8943 sql
8944 ));
8945 }
8946 }
8947
8948 Ok(if terms.is_empty() {
8949 None
8950 } else {
8951 Some(terms.join(", "))
8952 })
8953}
8954
8955fn always_present(rel: &RelationshipField, table: &postrust_core::schema_cache::Table) -> bool {
8967 use postrust_core::schema_cache::{Cardinality, Relationship};
8968 let Relationship::ForeignKey { cardinality, .. } = &rel.relationship else {
8969 return false;
8970 };
8971 match cardinality {
8972 Cardinality::M2O { .. } => {}
8973 Cardinality::O2O {
8974 is_parent: false, ..
8975 } => {}
8976 _ => return false,
8977 }
8978 let columns = cardinality.columns();
8979 !columns.is_empty()
8980 && columns.iter().all(|(local, _)| {
8981 table
8982 .get_column(local)
8983 .is_some_and(|column| !column.nullable)
8984 })
8985}
8986
8987fn child_holds_the_key(relationship: &postrust_core::schema_cache::Relationship) -> bool {
8995 use postrust_core::schema_cache::{Cardinality, Relationship};
8996 match relationship {
8997 Relationship::ForeignKey { cardinality, .. } => match cardinality {
8998 Cardinality::M2O { .. } => false,
8999 Cardinality::O2O { is_parent, .. } => *is_parent,
9000 _ => true,
9001 },
9002 Relationship::Computed { .. } => true,
9003 }
9004}
9005
9006fn computed_session_argument(
9008 cache: &SchemaCache,
9009 function: &postrust_core::api_request::QualifiedIdentifier,
9010 row_argument: &str,
9011) -> Option<String> {
9012 let _ = row_argument;
9013 cache
9014 .routines
9015 .get(function)
9016 .into_iter()
9017 .flatten()
9018 .find(|routine| routine.name == function.name)?
9019 .params
9020 .iter()
9021 .find(|param| {
9022 param.name == "hasura_session" && matches!(param.param_type.as_str(), "json" | "jsonb")
9023 })
9024 .map(|param| param.name.clone())
9025}
9026
9027fn computed_args_type_name(type_name: &str, field: &str) -> String {
9029 format!("{}_{}_args", type_name, field)
9030}
9031
9032fn computed_arguments(
9039 relationship: &RelationshipField,
9040 plan: &postrust_core::embed::EmbedPlan,
9041 parent_row: &str,
9042 given: Option<&serde_json::Value>,
9043 param_idx: &mut usize,
9044 values: &mut Vec<serde_json::Value>,
9045 schema_cache: &SchemaCache,
9046) -> Result<Option<String>, async_graphql::Error> {
9047 let (Some(function), Some(row_argument)) = (&plan.function, &plan.row_argument) else {
9048 return Ok(None);
9049 };
9050 let session = schema_cache
9051 .routines
9052 .get(function)
9053 .into_iter()
9054 .flatten()
9055 .find(|routine| routine.name == function.name)
9056 .and_then(|routine| {
9057 routine
9058 .params
9059 .iter()
9060 .find(|param| {
9061 param.name == "hasura_session"
9062 && matches!(param.param_type.as_str(), "json" | "jsonb")
9063 })
9064 .map(|param| param.name.clone())
9065 });
9066 if relationship.arguments.is_empty() && session.is_none() {
9067 return Ok(None);
9068 }
9069
9070 let mut passed = vec![format!(
9071 "{} => {}",
9072 postrust_sql::escape_ident(row_argument),
9073 parent_row
9074 )];
9075 if let Some(session) = session {
9076 passed.push(format!(
9077 "{} => {}",
9078 postrust_sql::escape_ident(&session),
9079 SESSION_ARGUMENT
9080 ));
9081 }
9082 for (name, pg_type, required) in &relationship.arguments {
9083 match given.and_then(|given| given.get(name)) {
9084 None if *required => {
9085 return Err(async_graphql::Error::new(format!(
9086 "{} needs the argument \"{}\"",
9087 relationship.name, name
9088 )))
9089 }
9090 None => continue,
9092 Some(value) => {
9093 passed.push(format!(
9094 "{} => ${}::{}",
9095 postrust_sql::escape_ident(name),
9096 param_idx,
9097 pg_type
9098 ));
9099 *param_idx += 1;
9100 values.push(value.clone());
9101 }
9102 }
9103 }
9104 Ok(Some(passed.join(", ")))
9105}
9106
9107fn embed_arguments(field: async_graphql::SelectionField<'_>) -> HashMap<String, serde_json::Value> {
9109 field
9110 .arguments()
9111 .map(|args| {
9112 args.into_iter()
9113 .map(|(name, value)| {
9114 (
9115 name.to_string(),
9116 plain_numbers(value.into_json().unwrap_or(serde_json::Value::Null)),
9117 )
9118 })
9119 .collect()
9120 })
9121 .unwrap_or_default()
9122}
9123
9124type Narrowing = (String, Option<String>, Option<String>, Option<i64>, i64);
9128
9129#[allow(clippy::too_many_arguments)] fn embed_narrowing(
9136 arguments: &HashMap<String, serde_json::Value>,
9137 plan: &postrust_core::embed::EmbedPlan,
9138 schema_cache: &SchemaCache,
9139 relationships: &HashMap<String, Vec<RelationshipField>>,
9140 target_type: &str,
9141 child_alias: &str,
9142 max_rows: Option<i64>,
9143 param_idx: &mut usize,
9144 values: &mut Vec<serde_json::Value>,
9145 names: &crate::names::NameOverrides,
9146 caller: &crate::role::Caller<'_>,
9147) -> Result<Narrowing, async_graphql::Error> {
9148 let permission =
9152 permission_predicate(caller, names, &plan.foreign_schema, &plan.foreign_table)?;
9153 let requested = arguments.get("where").filter(|v| !v.is_null()).cloned();
9154 let child_where = match and_predicates(requested, permission) {
9155 Some(expression) => {
9156 let child_scope = WhereScope::for_alias(
9157 &plan.foreign_schema,
9158 &plan.foreign_table,
9159 child_alias,
9160 target_type,
9161 schema_cache,
9162 relationships,
9163 names,
9164 )
9165 .for_caller(*caller);
9166 let mut nested_alias = 0usize;
9167 build_condition(
9168 &expression,
9169 &child_scope,
9170 param_idx,
9171 values,
9172 &mut nested_alias,
9173 )?
9174 }
9175 None => None,
9176 };
9177
9178 let child_order = match arguments.get("order_by") {
9179 Some(order) if !order.is_null() => order_terms(
9180 order,
9181 schema_cache,
9182 &plan.foreign_schema,
9183 &plan.foreign_table,
9184 child_alias,
9185 names,
9186 )?,
9187 _ => None,
9188 };
9189
9190 let ceiling = match (
9195 max_rows,
9196 crate::role::read_limit(caller, names, &plan.foreign_schema, &plan.foreign_table),
9197 ) {
9198 (Some(configured), Some(granted)) => Some(configured.min(granted)),
9199 (only, None) | (None, only) => only,
9200 };
9201 let child_limit = match arguments.get("limit").and_then(|v| v.as_i64()) {
9202 Some(requested) => match ceiling {
9203 Some(ceiling) => Some(requested.min(ceiling)),
9204 None => Some(requested),
9205 },
9206 None => ceiling,
9207 };
9208 let child_offset = arguments
9209 .get("offset")
9210 .and_then(|v| v.as_i64())
9211 .unwrap_or(0);
9212
9213 let requested = match arguments.get("distinct_on") {
9217 Some(serde_json::Value::Array(items)) => items
9218 .iter()
9219 .filter_map(|item| item.as_str())
9220 .collect::<Vec<_>>(),
9221 Some(serde_json::Value::String(one)) => vec![one.as_str()],
9222 _ => Vec::new(),
9223 };
9224 let mut distinct_on = Vec::with_capacity(requested.len());
9225 if !requested.is_empty() {
9226 let qi = postrust_core::api_request::QualifiedIdentifier::new(
9227 &plan.foreign_schema,
9228 &plan.foreign_table,
9229 );
9230 let table = schema_cache.get_table(&qi).ok_or_else(|| {
9231 async_graphql::Error::new(format!("unknown table \"{}\"", plan.foreign_table))
9232 })?;
9233 for field in requested {
9234 let column = table_column_for(names, table, field).to_string();
9235 if table.get_column(&column).is_none() {
9236 return Err(async_graphql::Error::new(format!(
9237 "cannot take distinct on unknown column \"{}\" of \"{}\"",
9238 field, plan.foreign_table
9239 )));
9240 }
9241 distinct_on.push(format!(
9242 "{}.{}",
9243 postrust_sql::escape_ident(child_alias),
9244 postrust_sql::escape_ident(&column)
9245 ));
9246 }
9247 }
9248 let (distinct, child_order) = distinct_on_clause(&distinct_on, child_order.as_deref())?;
9249
9250 Ok((
9251 distinct,
9252 child_where,
9253 child_order,
9254 child_limit,
9255 child_offset,
9256 ))
9257}
9258
9259#[allow(clippy::too_many_arguments)] fn build_embed_expressions<'a>(
9261 caller: &crate::role::Caller<'_>,
9262 schema_cache: &SchemaCache,
9263 relationships: &HashMap<String, Vec<RelationshipField>>,
9264 type_name: &str,
9265 parent_alias: &str,
9266 fields: impl IntoIterator<Item = SelectionField<'a>>,
9267 max_rows: Option<i64>,
9268 alias_counter: &mut usize,
9269 param_idx: &mut usize,
9270 values: &mut Vec<serde_json::Value>,
9271 names: &crate::names::NameOverrides,
9272) -> Result<Vec<(String, String)>, async_graphql::Error> {
9273 let Some(available) = relationships.get(type_name) else {
9274 return Ok(Vec::new());
9275 };
9276
9277 let mut expressions = Vec::new();
9278
9279 for field in fields {
9280 let aggregate_of = field
9284 .name()
9285 .strip_suffix("_aggregate")
9286 .and_then(|name| available.iter().find(|r| r.name == name && r.is_list));
9287
9288 if let Some(rel) = aggregate_of {
9289 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9290 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9291 *alias_counter += 1;
9292 let child_alias = format!("a{}", alias_counter);
9293
9294 let arguments = embed_arguments(field);
9298 let (child_distinct, child_where, child_order, child_limit, child_offset) =
9299 embed_narrowing(
9300 &arguments,
9301 &plan,
9302 schema_cache,
9303 relationships,
9304 &rel.target_type,
9305 &child_alias,
9306 max_rows,
9307 param_idx,
9308 values,
9309 names,
9310 caller,
9311 )?;
9312
9313 let child_table =
9314 schema_cache.get_table(&postrust_core::api_request::QualifiedIdentifier::new(
9315 &plan.foreign_schema,
9316 &plan.foreign_table,
9317 ));
9318 let select = nested_aggregate_select(
9319 caller,
9320 field,
9321 &child_alias,
9322 schema_cache,
9323 relationships,
9324 &rel.target_type,
9325 child_table,
9326 max_rows,
9327 alias_counter,
9328 param_idx,
9329 values,
9330 names,
9331 )?;
9332
9333 let call = computed_arguments(
9334 rel,
9335 &plan,
9336 &postrust_sql::escape_ident(parent_alias),
9337 arguments.get("args"),
9338 param_idx,
9339 values,
9340 schema_cache,
9341 )?;
9342 let expression = plan
9343 .aggregate_expression(
9344 parent_alias,
9345 &postrust_sql::escape_ident(parent_alias),
9346 &child_alias,
9347 &select,
9348 child_limit,
9349 child_offset,
9350 child_where.as_deref(),
9351 child_order.as_deref(),
9352 call.as_deref(),
9353 &child_distinct,
9354 postrust_core::EmbedRendering::Verbatim,
9358 )
9359 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9360 expressions.push((field.name().to_string(), expression));
9361 continue;
9362 }
9363
9364 let Some(rel) = available.iter().find(|r| r.name == field.name()) else {
9365 continue;
9366 };
9367
9368 let plan = postrust_core::embed::EmbedPlan::resolve(&rel.relationship, schema_cache)
9369 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9370
9371 *alias_counter += 1;
9372 let child_alias = format!("e{}", alias_counter);
9373
9374 let nested = build_embed_expressions(
9375 caller,
9376 schema_cache,
9377 relationships,
9378 &rel.target_type,
9379 &child_alias,
9380 field.selection_set(),
9381 max_rows,
9382 alias_counter,
9383 param_idx,
9384 values,
9385 names,
9386 )?;
9387
9388 let arguments = embed_arguments(field);
9390 let (child_distinct, child_where, child_order, child_limit, child_offset) =
9391 embed_narrowing(
9392 &arguments,
9393 &plan,
9394 schema_cache,
9395 relationships,
9396 &rel.target_type,
9397 &child_alias,
9398 max_rows,
9399 param_idx,
9400 values,
9401 names,
9402 caller,
9403 )?;
9404
9405 let child_relationships = relationships.get(&rel.target_type);
9408 let child_qi = postrust_core::api_request::QualifiedIdentifier::new(
9409 &plan.foreign_schema,
9410 &plan.foreign_table,
9411 );
9412 let child_table = schema_cache.get_table(&child_qi);
9413 let mut parts: Vec<String> = Vec::new();
9414 for sub in field.selection_set() {
9415 let name = sub.name();
9416 let is_relationship = child_relationships
9417 .map(|rels| {
9418 rels.iter().any(|r| {
9419 r.name == name || (r.is_list && format!("{}_aggregate", r.name) == name)
9420 })
9421 })
9422 .unwrap_or(false);
9423 if is_relationship || name == "__typename" {
9427 continue;
9428 }
9429 let column = child_table
9432 .map(|t| table_column_for(names, t, name))
9433 .unwrap_or(name);
9434 let computed = child_table
9435 .filter(|t| t.get_column(column).is_none())
9436 .and_then(|t| {
9437 let function = names
9438 .computed_source(&t.schema, &t.name, name)
9439 .unwrap_or(name);
9440 t.get_computed_column(function)
9441 });
9442 match computed {
9443 Some(definition) => parts.push(format!(
9444 "{} AS {}",
9445 computed_call(
9446 definition,
9447 &format!("{}.*", postrust_sql::escape_ident(&child_alias)),
9448 &postrust_sql::escape_ident(&child_alias),
9449 ),
9450 postrust_sql::escape_ident(name)
9451 )),
9452 None if column != name => parts.push(format!(
9455 "{} AS {}",
9456 postrust_sql::escape_ident(column),
9457 postrust_sql::escape_ident(name)
9458 )),
9459 None => parts.push(postrust_sql::escape_ident(name)),
9460 }
9461 }
9462 if parts.is_empty() && nested.is_empty() {
9463 parts.push(
9464 child_table
9465 .and_then(|table| rename_projection(table, &child_alias, names))
9466 .unwrap_or_else(|| format!("{}.*", postrust_sql::escape_ident(&child_alias))),
9467 );
9468 }
9469 for (field_name, expression) in nested {
9470 parts.push(format!(
9471 "{} AS {}",
9472 expression,
9473 postrust_sql::escape_ident(&field_name)
9474 ));
9475 }
9476
9477 let computed_call_arguments = computed_arguments(
9478 rel,
9479 &plan,
9480 &postrust_sql::escape_ident(parent_alias),
9481 arguments.get("args"),
9482 param_idx,
9483 values,
9484 schema_cache,
9485 )?;
9486 let expression = plan
9487 .embed_expression(
9488 parent_alias,
9489 &postrust_sql::escape_ident(parent_alias),
9493 &child_alias,
9494 &format!("{}{}", child_distinct, parts.join(", ")),
9495 child_limit,
9496 child_offset,
9497 child_where.as_deref(),
9498 child_order.as_deref(),
9499 computed_call_arguments.as_deref(),
9500 postrust_core::EmbedRendering::Verbatim,
9501 )
9502 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9503
9504 expressions.push((rel.name.clone(), expression));
9505 }
9506
9507 Ok(expressions)
9508}
9509
9510struct EmbedContext<'c> {
9516 schema_cache: &'c SchemaCache,
9517 relationships: &'c HashMap<String, Vec<RelationshipField>>,
9518 max_rows: Option<i64>,
9519}
9520
9521fn embed_relationships<'f>(
9527 conn: &'f mut sqlx::PgConnection,
9528 ctx: &'f EmbedContext<'f>,
9529 type_name: &'f str,
9530 selection: async_graphql::SelectionField<'f>,
9531 rows: &'f mut [serde_json::Value],
9532) -> futures::future::BoxFuture<'f, Result<(), async_graphql::Error>> {
9533 Box::pin(async move {
9534 if rows.is_empty() {
9535 return Ok(());
9536 }
9537
9538 let Some(available) = ctx.relationships.get(type_name) else {
9539 return Ok(());
9540 };
9541
9542 for requested in selection.selection_set() {
9543 let Some(rel) = available.iter().find(|r| r.name == requested.name()) else {
9544 continue;
9545 };
9546
9547 let plan =
9548 postrust_core::embed::EmbedPlan::resolve(&rel.relationship, ctx.schema_cache)
9549 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9550
9551 let keys = postrust_core::embed::parent_keys(rows, &plan.local_column);
9552
9553 let mut child_columns: Vec<String> = Vec::new();
9563 let mut project_everything = false;
9564 let child_relationships = ctx.relationships.get(&rel.target_type);
9565 for field in requested.selection_set() {
9566 let name = field.name();
9567 match child_relationships.and_then(|rels| rels.iter().find(|r| r.name == name)) {
9568 Some(nested_rel) => {
9569 match postrust_core::embed::EmbedPlan::resolve(
9570 &nested_rel.relationship,
9571 ctx.schema_cache,
9572 ) {
9573 Ok(nested_plan) => child_columns.push(nested_plan.local_column),
9574 Err(_) => project_everything = true,
9575 }
9576 }
9577 None if name == "__typename" => {}
9579 None => child_columns.push(name.to_string()),
9580 }
9581 }
9582 if project_everything || child_columns.is_empty() {
9583 child_columns.clear();
9584 }
9585
9586 let mut grouped = if keys.is_empty() {
9587 std::collections::HashMap::new()
9588 } else {
9589 let sql = plan
9590 .children_grouped_sql(ctx.max_rows, &child_columns)
9591 .map_err(|e| async_graphql::Error::new(e.to_string()))?;
9592
9593 let fetched = sqlx::query(&sql)
9594 .bind(&keys)
9595 .fetch_all(&mut *conn)
9596 .await
9597 .map_err(database_error)?;
9598
9599 use sqlx::Row;
9602 let pairs: Vec<(serde_json::Value, serde_json::Value)> = fetched
9603 .into_iter()
9604 .filter_map(|row| {
9605 Some((
9606 row.try_get::<serde_json::Value, _>(0).ok()?,
9607 row.try_get::<serde_json::Value, _>(1).ok()?,
9608 ))
9609 })
9610 .collect();
9611
9612 postrust_core::embed::group_from_aggregated(pairs)
9613 };
9614
9615 let has_deeper_embed = ctx
9620 .relationships
9621 .get(&rel.target_type)
9622 .map(|rels| {
9623 requested
9624 .selection_set()
9625 .any(|field| rels.iter().any(|r| r.name == field.name()))
9626 })
9627 .unwrap_or(false);
9628
9629 if has_deeper_embed {
9630 let mut order: Vec<(String, usize)> = Vec::with_capacity(grouped.len());
9631 let mut flat: Vec<serde_json::Value> = Vec::new();
9632 for (key, children) in grouped.drain() {
9633 order.push((key, children.len()));
9634 flat.extend(children);
9635 }
9636
9637 embed_relationships(&mut *conn, ctx, &rel.target_type, requested, &mut flat)
9638 .await?;
9639
9640 let mut rest = flat.into_iter();
9641 for (key, count) in order {
9642 grouped.insert(key, rest.by_ref().take(count).collect());
9643 }
9644 }
9645
9646 for row in rows.iter_mut() {
9647 postrust_core::embed::attach_to_parent(row, &rel.name, &plan, &grouped);
9648 }
9649 }
9650
9651 Ok(())
9652 })
9653}
9654
9655fn pk_where_from_args(
9660 ctx: &ResolverContext<'_>,
9661 schema_name: &str,
9662 table_name: &str,
9663 pk_columns: &[(String, String)],
9664 names: &crate::names::NameOverrides,
9665) -> Result<serde_json::Value, async_graphql::Error> {
9666 if pk_columns.is_empty() {
9667 return Err(async_graphql::Error::new(format!(
9668 "\"{}\" has no primary key, so it cannot be mutated by key",
9669 table_name
9670 )));
9671 }
9672
9673 let from_object = ctx
9677 .args
9678 .try_get("pk_columns")
9679 .ok()
9680 .map(|v| accessor_to_json(&v));
9681
9682 let mut conditions = serde_json::Map::new();
9683 for (col_name, _) in pk_columns {
9684 let field = names
9688 .column(schema_name, table_name, col_name)
9689 .unwrap_or(col_name);
9690 let value = match &from_object {
9691 Some(serde_json::Value::Object(map)) => map.get(field).cloned().ok_or_else(|| {
9692 async_graphql::Error::new(format!(
9693 "pk_columns is missing the key column \"{}\"",
9694 field
9695 ))
9696 })?,
9697 _ => {
9698 let arg = ctx.args.try_get(field).map_err(|_| {
9699 async_graphql::Error::new(format!(
9700 "missing required primary key argument \"{}\"",
9701 field
9702 ))
9703 })?;
9704 accessor_to_json(&arg)
9705 }
9706 };
9707 conditions.insert(field.to_string(), serde_json::json!({ "_eq": value }));
9708 }
9709
9710 Ok(serde_json::Value::Object(conditions))
9711}
9712
9713fn pk_argument_type(pg_type: &str) -> String {
9720 let rendered = crate::types::pg_type_to_graphql(pg_type).to_string();
9721 if rendered.starts_with('[') {
9722 "String".to_string()
9723 } else {
9724 rendered
9725 }
9726}
9727
9728fn exposed_pk_argument_type(
9733 names: &crate::names::NameOverrides,
9734 schema: &str,
9735 table: &str,
9736 column: &str,
9737 pg_type: &str,
9738) -> String {
9739 names
9740 .column_type(schema, table, column)
9741 .map(|given| given.to_string())
9742 .unwrap_or_else(|| pk_argument_type(pg_type))
9743}
9744
9745fn graphql_type_ref(type_str: &str) -> TypeRef {
9747 let is_list = type_str.starts_with('[');
9749 let is_nn = type_str.ends_with('!');
9750
9751 let inner = if is_list {
9753 let stripped = type_str
9754 .trim_end_matches('!') .trim_start_matches('[') .trim_end_matches(']'); stripped
9758 } else {
9759 type_str.trim_end_matches('!')
9760 };
9761
9762 let inner_nn = inner.ends_with('!');
9763 let base_type = inner.trim_end_matches('!');
9764
9765 if is_list {
9766 if is_nn {
9767 if inner_nn {
9768 TypeRef::named_nn_list_nn(base_type)
9769 } else {
9770 TypeRef::named_list_nn(base_type)
9771 }
9772 } else if inner_nn {
9773 TypeRef::named_nn_list(base_type)
9774 } else {
9775 TypeRef::named_list(base_type)
9776 }
9777 } else if is_nn {
9778 TypeRef::named_nn(base_type)
9779 } else {
9780 TypeRef::named(base_type)
9781 }
9782}
9783
9784const NUMBER_AS_TEXT: &str = "$serde_json::private::Number";
9794
9795fn number_kept_as_text(
9798 map: &serde_json::Map<String, serde_json::Value>,
9799) -> Option<serde_json::Value> {
9800 if map.len() != 1 {
9801 return None;
9802 }
9803 let text = map.get(NUMBER_AS_TEXT)?.as_str()?;
9804 serde_json::from_str::<serde_json::Value>(text)
9805 .ok()
9806 .filter(|value| value.is_number())
9807}
9808
9809fn plain_numbers(value: serde_json::Value) -> serde_json::Value {
9811 match value {
9812 serde_json::Value::Object(map) => match number_kept_as_text(&map) {
9813 Some(number) => number,
9814 None => serde_json::Value::Object(
9815 map.into_iter()
9816 .map(|(k, v)| (k, plain_numbers(v)))
9817 .collect(),
9818 ),
9819 },
9820 serde_json::Value::Array(items) => {
9821 serde_json::Value::Array(items.into_iter().map(plain_numbers).collect())
9822 }
9823 other => other,
9824 }
9825}
9826
9827fn accessor_to_json(accessor: &ValueAccessor<'_>) -> serde_json::Value {
9829 if accessor.is_null() {
9831 serde_json::Value::Null
9832 } else if let Ok(b) = accessor.boolean() {
9833 serde_json::Value::Bool(b)
9834 } else if let Ok(i) = accessor.i64() {
9835 serde_json::Value::Number(i.into())
9836 } else if let Ok(f) = accessor.f64() {
9837 serde_json::Number::from_f64(f)
9838 .map(serde_json::Value::Number)
9839 .unwrap_or(serde_json::Value::Null)
9840 } else if let Ok(s) = accessor.string() {
9841 serde_json::Value::String(s.to_string())
9842 } else if let Ok(name) = accessor.enum_name() {
9843 serde_json::Value::String(name.to_string())
9846 } else if let Ok(list) = accessor.list() {
9847 serde_json::Value::Array(list.iter().map(|v| accessor_to_json(&v)).collect())
9848 } else if let Ok(obj) = accessor.object() {
9849 let map: serde_json::Map<String, serde_json::Value> = obj
9850 .iter()
9851 .map(|(k, v)| (k.to_string(), accessor_to_json(&v)))
9852 .collect();
9853 match number_kept_as_text(&map) {
9854 Some(number) => number,
9855 None => serde_json::Value::Object(map),
9856 }
9857 } else {
9858 serde_json::Value::Null
9859 }
9860}
9861
9862fn value_to_json(value: &Value) -> serde_json::Value {
9864 match value {
9865 Value::Null => serde_json::Value::Null,
9866 Value::Boolean(b) => serde_json::Value::Bool(*b),
9867 Value::Number(n) => {
9868 if let Some(i) = n.as_i64() {
9869 serde_json::Value::Number(i.into())
9870 } else if let Some(f) = n.as_f64() {
9871 serde_json::Value::Number(serde_json::Number::from_f64(f).unwrap())
9872 } else {
9873 serde_json::Value::Null
9874 }
9875 }
9876 Value::String(s) => serde_json::Value::String(s.clone()),
9877 Value::List(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
9878 Value::Object(obj) => {
9879 let map: serde_json::Map<String, serde_json::Value> = obj
9880 .iter()
9881 .map(|(k, v)| (k.to_string(), value_to_json(v)))
9882 .collect();
9883 serde_json::Value::Object(map)
9884 }
9885 Value::Binary(b) => serde_json::Value::String(base64::Engine::encode(
9886 &base64::engine::general_purpose::STANDARD,
9887 b,
9888 )),
9889 Value::Enum(e) => serde_json::Value::String(e.to_string()),
9890 }
9891}
9892
9893fn json_to_value(json: serde_json::Value) -> Value {
9895 match json {
9896 serde_json::Value::Null => Value::Null,
9897 serde_json::Value::Bool(b) => Value::Boolean(b),
9898 serde_json::Value::Number(n) => {
9899 if let Some(i) = n.as_i64() {
9900 Value::Number(i.into())
9901 } else if let Some(f) = n.as_f64() {
9902 Value::Number(async_graphql::Number::from_f64(f).unwrap())
9903 } else {
9904 Value::Null
9905 }
9906 }
9907 serde_json::Value::String(s) => Value::String(s),
9908 serde_json::Value::Array(arr) => Value::List(arr.into_iter().map(json_to_value).collect()),
9909 serde_json::Value::Object(obj) => {
9910 let map: indexmap::IndexMap<async_graphql::Name, Value> = obj
9911 .into_iter()
9912 .map(|(k, v)| (async_graphql::Name::new(k), json_to_value(v)))
9913 .collect();
9914 Value::Object(map)
9915 }
9916 }
9917}
9918
9919#[cfg(test)]
9929mod tests {
9930 use super::*;
9931 use indexmap::IndexMap;
9932 use postrust_core::schema_cache::{Cardinality, Column, Relationship, Table};
9933 use postrust_core::QualifiedIdentifier;
9934 use std::collections::{HashMap, HashSet};
9935
9936 fn entity_key_column(pg_type: &str, explicit_id: bool) -> EntityKeyColumn {
9937 EntityKeyColumn {
9938 field_name: "id".to_string(),
9939 column_name: "id".to_string(),
9940 pg_type: pg_type.to_string(),
9941 explicit_id,
9942 }
9943 }
9944
9945 #[test]
9946 fn federation_integer_keys_reject_strings() {
9947 let column = entity_key_column("int4", false);
9948
9949 assert_eq!(
9950 normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap(),
9951 serde_json::json!(2)
9952 );
9953 let error = normalize_entity_key_value("users", &column, &serde_json::json!("2"))
9954 .expect_err("a GraphQL Int cannot be a string");
9955 assert_eq!(
9956 error.message,
9957 "entity representation for \"users\" has invalid key field \"id\": expected a value compatible with PostgreSQL type \"int4\", found a string"
9958 );
9959 }
9960
9961 #[test]
9962 fn federation_bigint_keys_accept_exact_decimal_strings() {
9963 let column = entity_key_column("int8", false);
9964
9965 assert_eq!(
9966 normalize_entity_key_value("users", &column, &serde_json::json!(7)).unwrap(),
9967 serde_json::json!(7)
9968 );
9969 assert_eq!(
9970 normalize_entity_key_value("users", &column, &serde_json::json!("9007199254740993"))
9971 .unwrap(),
9972 serde_json::json!(9_007_199_254_740_993_i64)
9973 );
9974 assert!(normalize_entity_key_value(
9975 "users",
9976 &column,
9977 &serde_json::json!("9223372036854775808")
9978 )
9979 .is_err());
9980 assert!(normalize_entity_key_value("users", &column, &serde_json::json!("7.5")).is_err());
9981 }
9982
9983 #[test]
9984 fn federation_numeric_keys_accept_exact_decimal_strings() {
9985 let column = entity_key_column("numeric", false);
9986
9987 assert_eq!(
9988 normalize_entity_key_value("users", &column, &serde_json::json!(7.5)).unwrap(),
9989 serde_json::json!("7.5")
9990 );
9991 assert_eq!(
9992 normalize_entity_key_value(
9993 "users",
9994 &column,
9995 &serde_json::json!("9007199254740993.123456789")
9996 )
9997 .unwrap(),
9998 serde_json::json!("9007199254740993.123456789")
9999 );
10000 assert!(
10001 normalize_entity_key_value("users", &column, &serde_json::json!("not-a-number"))
10002 .is_err()
10003 );
10004 }
10005
10006 #[test]
10007 fn federation_integer_backed_ids_have_one_normal_form() {
10008 let column = entity_key_column("int4", true);
10009
10010 let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
10011 let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
10012 assert_eq!(number, serde_json::json!(2));
10013 assert_eq!(string, number);
10014 assert!(normalize_entity_key_value("users", &column, &serde_json::json!("two")).is_err());
10015 }
10016
10017 #[test]
10018 fn federation_text_backed_ids_normalize_integer_input_to_text() {
10019 let column = entity_key_column("text", true);
10020
10021 let number = normalize_entity_key_value("users", &column, &serde_json::json!(2)).unwrap();
10022 let string = normalize_entity_key_value("users", &column, &serde_json::json!("2")).unwrap();
10023 assert_eq!(number, serde_json::json!("2"));
10024 assert_eq!(string, number);
10025 }
10026
10027 #[test]
10028 fn federation_uuid_backed_ids_are_validated_and_canonicalized() {
10029 let column = entity_key_column("uuid", true);
10030
10031 assert_eq!(
10032 normalize_entity_key_value(
10033 "users",
10034 &column,
10035 &serde_json::json!("550E8400-E29B-41D4-A716-446655440000")
10036 )
10037 .unwrap(),
10038 serde_json::json!("550e8400-e29b-41d4-a716-446655440000")
10039 );
10040 assert!(
10041 normalize_entity_key_value("users", &column, &serde_json::json!("not-a-uuid")).is_err()
10042 );
10043 }
10044
10045 fn create_test_table(name: &str) -> Table {
10046 let mut columns = IndexMap::new();
10047 columns.insert(
10048 "id".into(),
10049 Column {
10050 name: "id".into(),
10051 description: None,
10052 nullable: false,
10053 data_type: "integer".into(),
10054 nominal_type: "int4".into(),
10055 max_len: None,
10056 default: Some("nextval('id_seq')".into()),
10057 enum_values: vec![],
10058 is_pk: true,
10059 position: 1,
10060 always_generated: false,
10061 domain_type: None,
10062 },
10063 );
10064 columns.insert(
10065 "name".into(),
10066 Column {
10067 name: "name".into(),
10068 description: None,
10069 nullable: false,
10070 data_type: "text".into(),
10071 nominal_type: "text".into(),
10072 max_len: None,
10073 default: None,
10074 enum_values: vec![],
10075 is_pk: false,
10076 position: 2,
10077 always_generated: false,
10078 domain_type: None,
10079 },
10080 );
10081
10082 Table {
10083 schema: "public".into(),
10084 name: name.into(),
10085 description: None,
10086 is_view: false,
10087 insertable: true,
10088 updatable: true,
10089 deletable: true,
10090 pk_cols: vec!["id".into()],
10091 unique_constraints: Vec::new(),
10092 columns,
10093 computed_columns: Default::default(),
10094 is_partitioned: false,
10095 }
10096 }
10097
10098 fn create_test_schema_cache() -> SchemaCache {
10099 let mut tables = HashMap::new();
10100 let users = create_test_table("users");
10101 tables.insert(users.qualified_identifier(), users);
10102
10103 SchemaCache {
10104 tables,
10105 relationships: HashMap::new(),
10106 routines: HashMap::new(),
10107 timezones: HashSet::new(),
10108 media_handlers: HashMap::new(),
10109 pg_version: 150000,
10110 representations: Default::default(),
10111 }
10112 }
10113
10114 fn add_test_table(cache: &mut SchemaCache, name: &str) {
10115 let table = create_test_table(name);
10116 cache.tables.insert(table.qualified_identifier(), table);
10117 }
10118
10119 fn add_test_posts_relationship(cache: &mut SchemaCache) {
10120 let users = QualifiedIdentifier::new("public", "users");
10121 cache.relationships.insert(
10122 (users.clone(), "public".into()),
10123 vec![Relationship::ForeignKey {
10124 table: users,
10125 foreign_table: QualifiedIdentifier::new("public", "posts"),
10126 is_self: false,
10127 cardinality: Cardinality::O2M {
10128 constraint: "posts_user_id_fkey".into(),
10129 columns: vec![("id".into(), "user_id".into())],
10130 },
10131 table_is_view: false,
10132 foreign_table_is_view: false,
10133 constraint_name: "posts_user_id_fkey".into(),
10134 }],
10135 );
10136 }
10137
10138 #[test]
10143 fn test_graphql_type_ref_simple() {
10144 let _type_ref = graphql_type_ref("String");
10145 }
10147
10148 #[test]
10149 fn test_graphql_type_ref_non_null() {
10150 let _type_ref = graphql_type_ref("String!");
10151 }
10152
10153 #[test]
10154 fn test_graphql_type_ref_list() {
10155 let _type_ref = graphql_type_ref("[String]");
10156 }
10157
10158 #[test]
10159 fn test_graphql_type_ref_list_non_null() {
10160 let _type_ref = graphql_type_ref("[String!]!");
10161 }
10162
10163 #[test]
10168 fn test_value_to_json_null() {
10169 let value = Value::Null;
10170 let json = value_to_json(&value);
10171 assert_eq!(json, serde_json::Value::Null);
10172 }
10173
10174 #[test]
10175 fn test_value_to_json_boolean() {
10176 let value = Value::Boolean(true);
10177 let json = value_to_json(&value);
10178 assert_eq!(json, serde_json::Value::Bool(true));
10179 }
10180
10181 #[test]
10182 fn test_value_to_json_number() {
10183 let value = Value::Number(42.into());
10184 let json = value_to_json(&value);
10185 assert_eq!(json, serde_json::json!(42));
10186 }
10187
10188 #[test]
10189 fn test_value_to_json_string() {
10190 let value = Value::String("hello".to_string());
10191 let json = value_to_json(&value);
10192 assert_eq!(json, serde_json::Value::String("hello".to_string()));
10193 }
10194
10195 #[test]
10196 fn test_value_to_json_list() {
10197 let value = Value::List(vec![Value::Number(1.into()), Value::Number(2.into())]);
10198 let json = value_to_json(&value);
10199 assert_eq!(json, serde_json::json!([1, 2]));
10200 }
10201
10202 #[test]
10203 fn test_json_to_value_null() {
10204 let json = serde_json::Value::Null;
10205 let value = json_to_value(json);
10206 assert!(matches!(value, Value::Null));
10207 }
10208
10209 #[test]
10210 fn test_json_to_value_boolean() {
10211 let json = serde_json::Value::Bool(false);
10212 let value = json_to_value(json);
10213 assert!(matches!(value, Value::Boolean(false)));
10214 }
10215
10216 #[test]
10217 fn test_json_to_value_number() {
10218 let json = serde_json::json!(123);
10219 let value = json_to_value(json);
10220 assert!(matches!(value, Value::Number(_)));
10221 }
10222
10223 #[test]
10224 fn test_json_to_value_string() {
10225 let json = serde_json::Value::String("test".to_string());
10226 let value = json_to_value(json);
10227 assert!(matches!(value, Value::String(_)));
10228 }
10229
10230 #[test]
10231 fn test_json_to_value_array() {
10232 let json = serde_json::json!([1, 2, 3]);
10233 let value = json_to_value(json);
10234 assert!(matches!(value, Value::List(_)));
10235 }
10236
10237 #[test]
10238 fn test_json_to_value_object() {
10239 let json = serde_json::json!({"key": "value"});
10240 let value = json_to_value(json);
10241 assert!(matches!(value, Value::Object(_)));
10242 }
10243
10244 #[test]
10249 fn test_build_dynamic_schema() {
10250 let cache = create_test_schema_cache();
10251 let config = SchemaConfig::default();
10252 let generated = build_schema(&cache, &config);
10253
10254 let result = build_dynamic_schema(
10255 &generated,
10256 &cache,
10257 None,
10258 None,
10259 Arc::new(Default::default()),
10260 std::time::Duration::from_secs(30),
10261 None,
10262 );
10263 if let Err(ref e) = result {
10264 eprintln!("Schema build error: {:?}", e);
10265 }
10266 assert!(result.is_ok(), "Schema build failed: {:?}", result.err());
10267 }
10268
10269 #[test]
10270 fn a_column_type_override_reaches_fields_and_key_arguments() {
10271 let cache = create_test_schema_cache();
10272 let names = crate::names::NameOverrides::parse(
10273 r#"{"tables": {"public.users": {"column_types": {"id": "ID"}}}}"#,
10274 )
10275 .unwrap();
10276 let config = SchemaConfig {
10277 names: names.clone(),
10278 ..SchemaConfig::default()
10279 };
10280 let generated = build_schema(&cache, &config);
10281 let schema = build_dynamic_schema(
10282 &generated,
10283 &cache,
10284 None,
10285 None,
10286 Arc::new(names),
10287 std::time::Duration::from_secs(30),
10288 None,
10289 )
10290 .expect("schema with an ID override should build");
10291 let sdl = schema.sdl();
10292
10293 let input_body = |name: &str| {
10294 sdl.split_once(&format!("input {name} {{"))
10295 .and_then(|(_, rest)| rest.split_once('}'))
10296 .map(|(body, _)| body.to_string())
10297 .unwrap_or_else(|| panic!("missing input {name}:\n{sdl}"))
10298 };
10299
10300 assert!(sdl.contains("id: ID!"), "object and input fields:\n{sdl}");
10301 assert!(
10302 sdl.contains("users_by_pk(id: ID!): users"),
10303 "query key argument:\n{sdl}"
10304 );
10305 assert!(
10306 sdl.contains("delete_users_by_pk(id: ID!): users"),
10307 "delete key argument:\n{sdl}"
10308 );
10309 assert!(
10310 input_body("users_pk_columns_input").contains("id: ID!"),
10311 "update key input:\n{sdl}"
10312 );
10313 assert!(
10314 input_body("users_bool_exp").contains("id: ID_comparison_exp"),
10315 "comparison input:\n{sdl}"
10316 );
10317 assert!(
10318 input_body("users_insert_input").contains("id: ID"),
10319 "write input:\n{sdl}"
10320 );
10321 }
10322
10323 #[test]
10324 fn a_type_prefix_names_the_complete_dynamic_schema() {
10325 let cache = create_test_schema_cache();
10326 let config = SchemaConfig {
10327 type_prefix: Some("test".into()),
10328 ..SchemaConfig::default()
10329 };
10330 let generated = build_schema(&cache, &config);
10331 let schema = build_dynamic_schema(
10332 &generated,
10333 &cache,
10334 None,
10335 None,
10336 Arc::new(Default::default()),
10337 std::time::Duration::from_secs(30),
10338 None,
10339 )
10340 .expect("a prefixed schema should build");
10341 let sdl = schema.sdl();
10342
10343 assert!(sdl.contains("type test_users "), "object type:\n{sdl}");
10344 assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10345 assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10346 assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10347 assert!(
10348 sdl.contains("test_users_aggregate"),
10349 "aggregate type:\n{sdl}"
10350 );
10351 assert!(sdl.contains("test_insert_users("), "mutation root:\n{sdl}");
10352 }
10353
10354 #[test]
10355 fn federation_is_absent_by_default() {
10356 let cache = create_test_schema_cache();
10357 let generated = build_schema(&cache, &SchemaConfig::default());
10358 let schema = build_dynamic_schema(
10359 &generated,
10360 &cache,
10361 None,
10362 None,
10363 Arc::new(Default::default()),
10364 std::time::Duration::from_secs(30),
10365 None,
10366 )
10367 .expect("a non-federated schema should build");
10368 let sdl = schema.sdl();
10369
10370 assert!(!sdl.contains("_Service"), "service type:\n{sdl}");
10371 assert!(!sdl.contains("_service"), "service field:\n{sdl}");
10372 assert!(!sdl.contains("@key"), "federation key:\n{sdl}");
10373 }
10374
10375 #[test]
10376 fn federation_entity_selections_respect_fragment_type_conditions() {
10377 let document = async_graphql::parser::parse_query(
10378 r#"
10379 query {
10380 _entities(representations: []) {
10381 __typename
10382 ... on authors { id items { title } }
10383 ...ShopFields
10384 ... { common }
10385 ...EntityName
10386 }
10387 }
10388 fragment ShopFields on shops { id items { sku } }
10389 fragment EntityName on _Entity { __typename }
10390 "#,
10391 )
10392 .expect("query parses");
10393 let (_, operation) = document.operations.iter().next().expect("one operation");
10394 let entity = operation
10395 .node
10396 .selection_set
10397 .node
10398 .items
10399 .iter()
10400 .find_map(|selection| match &selection.node {
10401 Selection::Field(field) if field.node.name.node == "_entities" => Some(&field.node),
10402 _ => None,
10403 })
10404 .expect("_entities field");
10405
10406 assert_eq!(
10407 entity_field_applicability(&entity.selection_set.node, &document.fragments, "authors"),
10408 vec![true, true, true, false, false, true, true]
10409 );
10410 assert_eq!(
10411 entity_field_applicability(&entity.selection_set.node, &document.fragments, "shops"),
10412 vec![true, false, false, true, true, true, true]
10413 );
10414 }
10415
10416 #[tokio::test]
10417 async fn federation_entity_lists_allow_null_members() {
10418 let entity =
10419 Object::new("widgets").field(Field::new("id", TypeRef::named_nn(TypeRef::INT), |_| {
10420 FieldFuture::new(async { Ok(Some(Value::from(2))) })
10421 }));
10422 let union = Union::new("_Entity").possible_type("widgets");
10423 let query = Object::new("Query").field(Field::new(
10424 "_entities",
10425 TypeRef::named_list_nn("_Entity"),
10426 |_| {
10427 FieldFuture::new(async {
10428 Ok(Some(FieldValue::list([
10429 FieldValue::NULL,
10430 FieldValue::NULL.with_type("widgets"),
10431 ])))
10432 })
10433 },
10434 ));
10435 let schema = Schema::build("Query", None, None)
10436 .extension(NullableFederationEntity)
10437 .register(entity)
10438 .register(union)
10439 .register(query)
10440 .finish()
10441 .expect("schema builds");
10442
10443 let response = schema
10444 .execute("{ _entities { ... on widgets { id } } }")
10445 .await;
10446
10447 assert!(response.errors.is_empty(), "{:?}", response.errors);
10448 assert_eq!(
10449 response.data,
10450 async_graphql::value!({ "_entities": [null, { "id": 2 }] })
10451 );
10452 }
10453
10454 #[test]
10455 fn federation_metadata_is_byte_for_byte_inert_when_disabled() {
10456 let cache = create_test_schema_cache();
10457 let sdl_for = |config: SchemaConfig| {
10458 let generated = build_schema(&cache, &config);
10459 build_dynamic_schema(
10460 &generated,
10461 &cache,
10462 None,
10463 None,
10464 Arc::new(config.names.clone()),
10465 std::time::Duration::from_secs(30),
10466 None,
10467 )
10468 .expect("schema should build")
10469 .sdl()
10470 };
10471
10472 let baseline = sdl_for(SchemaConfig {
10473 type_prefix: Some("test".into()),
10474 ..SchemaConfig::default()
10475 });
10476 let with_disabled_federation_metadata = sdl_for(SchemaConfig {
10477 enable_federation: false,
10478 type_prefix: Some("test".into()),
10479 names: crate::names::NameOverrides::parse(
10480 r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10481 )
10482 .expect("metadata"),
10483 ..SchemaConfig::default()
10484 });
10485
10486 assert_eq!(with_disabled_federation_metadata, baseline);
10487 assert!(
10488 with_disabled_federation_metadata.contains("type test_users "),
10489 "prefixed object type remains when federation is disabled:\n{with_disabled_federation_metadata}"
10490 );
10491 assert!(
10492 !with_disabled_federation_metadata.contains("_Service"),
10493 "service type:\n{with_disabled_federation_metadata}"
10494 );
10495 assert!(
10496 !with_disabled_federation_metadata.contains("@key"),
10497 "federation key:\n{with_disabled_federation_metadata}"
10498 );
10499 }
10500
10501 #[test]
10502 fn shared_entities_make_relationship_fields_shareable() {
10503 let federation_sdl = |shared: bool| {
10504 let mut cache = create_test_schema_cache();
10505 add_test_table(&mut cache, "posts");
10506 add_test_posts_relationship(&mut cache);
10507 let names = match shared {
10508 true => crate::names::NameOverrides::parse(
10509 r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10510 )
10511 .expect("metadata"),
10512 false => crate::names::NameOverrides::default(),
10513 };
10514 let config = SchemaConfig {
10515 enable_federation: true,
10516 type_prefix: Some("test".into()),
10517 names: names.clone(),
10518 ..SchemaConfig::default()
10519 };
10520 let generated = build_schema(&cache, &config);
10521 build_dynamic_schema(
10522 &generated,
10523 &cache,
10524 None,
10525 None,
10526 Arc::new(names),
10527 std::time::Duration::from_secs(30),
10528 None,
10529 )
10530 .expect("federated schema should build")
10531 .sdl_with_options(async_graphql::SDLExportOptions::new().federation())
10532 };
10533
10534 let shared = federation_sdl(true);
10535 assert!(
10536 shared.contains("): [test_posts!]! @shareable"),
10537 "shared relationship field:\n{shared}"
10538 );
10539 assert!(
10540 shared.contains("): test_posts_aggregate! @shareable"),
10541 "shared relationship aggregate field:\n{shared}"
10542 );
10543
10544 let not_shared = federation_sdl(false);
10545 assert!(
10546 not_shared.contains("): [test_posts!]!"),
10547 "non-shared relationship field:\n{not_shared}"
10548 );
10549 assert!(
10550 !not_shared.contains("): [test_posts!]! @shareable"),
10551 "non-shared relationship field must not be shareable:\n{not_shared}"
10552 );
10553 assert!(
10554 not_shared.contains("): test_posts_aggregate!"),
10555 "non-shared relationship aggregate field:\n{not_shared}"
10556 );
10557 assert!(
10558 !not_shared.contains("): test_posts_aggregate! @shareable"),
10559 "non-shared relationship aggregate must not be shareable:\n{not_shared}"
10560 );
10561 }
10562
10563 #[tokio::test]
10564 async fn federation_exposes_service_sdl_and_entity_keys() {
10565 let mut cache = create_test_schema_cache();
10566 add_test_table(&mut cache, "posts");
10567 let config = SchemaConfig {
10568 enable_federation: true,
10569 type_prefix: Some("test".into()),
10570 names: crate::names::NameOverrides::parse(
10571 r#"{"tables": {"public.users": {"federation": {"shared": true}}}}"#,
10572 )
10573 .expect("metadata"),
10574 ..SchemaConfig::default()
10575 };
10576 let generated = build_schema(&cache, &config);
10577 let schema = build_dynamic_schema(
10578 &generated,
10579 &cache,
10580 None,
10581 None,
10582 Arc::new(Default::default()),
10583 std::time::Duration::from_secs(30),
10584 None,
10585 )
10586 .expect("a federated schema should build");
10587 let sdl = schema.sdl();
10588 let federation_sdl =
10589 schema.sdl_with_options(async_graphql::SDLExportOptions::new().federation());
10590
10591 assert!(sdl.contains("_Service"), "service type:\n{sdl}");
10592 assert!(sdl.contains("_service"), "service field:\n{sdl}");
10593 assert!(
10594 federation_sdl.contains("type users @key(fields: \"id\")"),
10595 "users key:\n{federation_sdl}"
10596 );
10597 assert!(
10598 federation_sdl.contains("@shareable"),
10599 "shared field directive:\n{federation_sdl}"
10600 );
10601 assert!(sdl.contains("type users "), "shared object type:\n{sdl}");
10602 assert!(
10603 !sdl.contains("type test_users "),
10604 "prefixed shared object type:\n{sdl}"
10605 );
10606 assert!(sdl.contains("test_users("), "query root:\n{sdl}");
10607 assert!(sdl.contains("test_users_by_pk("), "by-PK root:\n{sdl}");
10608 assert!(sdl.contains("test_users_bool_exp"), "derived input:\n{sdl}");
10609 assert!(
10610 !sdl.contains("input users_bool_exp "),
10611 "unprefixed derived input:\n{sdl}"
10612 );
10613 assert!(
10614 sdl.contains("test_users_aggregate"),
10615 "aggregate type:\n{sdl}"
10616 );
10617 assert!(
10618 !sdl.contains("type users_aggregate "),
10619 "unprefixed aggregate type:\n{sdl}"
10620 );
10621 assert!(
10622 sdl.contains("test_users_mutation_response"),
10623 "mutation response type:\n{sdl}"
10624 );
10625 assert!(
10626 !sdl.contains("type users_mutation_response "),
10627 "unprefixed mutation response type:\n{sdl}"
10628 );
10629 assert!(
10630 sdl.contains("type test_posts {"),
10631 "non-shared object type remains prefixed:\n{sdl}"
10632 );
10633 assert!(
10634 !sdl.contains("type posts {"),
10635 "non-shared object type must not become unprefixed:\n{sdl}"
10636 );
10637 assert!(sdl.contains("test_posts("), "prefixed posts root:\n{sdl}");
10638 assert!(
10639 sdl.contains("test_posts_bool_exp"),
10640 "prefixed posts bool_exp:\n{sdl}"
10641 );
10642
10643 let response = schema.execute("{ _service { sdl } }").await;
10644 assert!(
10645 response.errors.is_empty(),
10646 "service query errors: {:?}",
10647 response.errors
10648 );
10649 let service = response.data.into_json().expect("service response JSON");
10650 let service_sdl = service
10651 .get("_service")
10652 .and_then(|value| value.get("sdl"))
10653 .and_then(serde_json::Value::as_str)
10654 .expect("service SDL string");
10655 assert!(
10656 service_sdl.contains("type Query"),
10657 "federation root type:\n{service_sdl}"
10658 );
10659 assert!(
10660 service_sdl.contains("test_users("),
10661 "query fields are on the federation root:\n{service_sdl}"
10662 );
10663 assert!(
10664 service_sdl.contains("test_posts("),
10665 "non-shared query fields are on the federation root:\n{service_sdl}"
10666 );
10667 assert!(
10668 !service_sdl.contains("type query_root"),
10669 "Apollo composition treats query_root as an ordinary object without a schema definition:\n{service_sdl}"
10670 );
10671 assert!(
10672 service_sdl.contains("type users @key(fields: \"id\")"),
10673 "{service_sdl}"
10674 );
10675 assert!(
10676 service_sdl.contains("type test_posts @key(fields: \"id\")"),
10677 "{service_sdl}"
10678 );
10679 }
10680
10681 #[test]
10684 fn exists_is_a_subselect_over_another_table() {
10685 let cache = create_test_schema_cache();
10686 let names = crate::names::NameOverrides::default();
10687 let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10688 let scope = WhereScope::table("public", "users", "users", &names)
10689 .with_resolution(&cache, &relationships);
10690
10691 let mut param_idx = 1usize;
10692 let mut values: Vec<serde_json::Value> = Vec::new();
10693 let mut aliases = 0usize;
10694 let sql = build_condition(
10695 &serde_json::json!({
10696 "_exists": {"_table": "users", "_where": {"name": {"_eq": "Ann"}}}
10697 }),
10698 &scope,
10699 &mut param_idx,
10700 &mut values,
10701 &mut aliases,
10702 )
10703 .expect("compiles")
10704 .expect("constrains something");
10705
10706 assert_eq!(
10707 sql,
10708 "EXISTS (SELECT 1 FROM \"public\".\"users\" AS \"pgrst_exists_1\" \
10709 WHERE \"pgrst_exists_1\".\"name\" = $1::text)"
10710 );
10711 assert_eq!(values, vec![serde_json::json!("Ann")]);
10712 }
10713
10714 #[test]
10716 fn exists_names_a_table_the_server_has() {
10717 let cache = create_test_schema_cache();
10718 let names = crate::names::NameOverrides::default();
10719 let relationships: HashMap<String, Vec<RelationshipField>> = HashMap::new();
10720 let scope = WhereScope::table("public", "users", "users", &names)
10721 .with_resolution(&cache, &relationships);
10722 let error = build_condition(
10723 &serde_json::json!({"_exists": {"_table": "nowhere", "_where": {}}}),
10724 &scope,
10725 &mut 1,
10726 &mut Vec::new(),
10727 &mut 0,
10728 )
10729 .expect_err("a table that is not there is an error");
10730 assert!(
10731 error.message.contains("public.nowhere"),
10732 "{}",
10733 error.message
10734 );
10735 }
10736
10737 #[test]
10744 fn a_table_a_role_may_count_and_not_read_has_a_count_and_no_rows() {
10745 let cache = create_test_schema_cache();
10746 let names = crate::names::NameOverrides::parse(
10747 r#"{"tables": {"public.users": {"permissions":
10748 {"counter": {"select": {"columns": [], "filter": {},
10749 "allow_aggregations": true}}}}}}"#,
10750 )
10751 .unwrap();
10752 let view = crate::role::cache_for_role(&cache, &names, "counter", false);
10753 assert!(!view.tables.is_empty(), "the table is there to be counted");
10754
10755 let config = SchemaConfig {
10756 names: names.clone(),
10757 role: Some("counter".to_string()),
10758 ..SchemaConfig::default()
10759 };
10760 let generated = build_schema(&view, &config);
10761 let schema = build_dynamic_schema(
10762 &generated,
10763 &view,
10764 None,
10765 None,
10766 Arc::new(names),
10767 std::time::Duration::from_secs(30),
10768 Some("counter"),
10769 )
10770 .expect("a role that may only count still has a buildable schema");
10771 let sdl = schema.sdl();
10772
10773 assert!(
10774 sdl.contains("users_aggregate"),
10775 "the count is there:\n{}",
10776 sdl
10777 );
10778 assert!(sdl.contains("count: Int!"), "and it is a count:\n{}", sdl);
10779 assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10781 assert!(
10782 !sdl.contains("nodes"),
10783 "and no rows beside the numbers:\n{}",
10784 sdl
10785 );
10786 assert!(
10787 !sdl.contains("users_select_column"),
10788 "no column enum to order by or count over:\n{}",
10789 sdl
10790 );
10791 assert!(!sdl.contains("users_max_fields"), "no max:\n{}", sdl);
10793 }
10794
10795 #[test]
10802 fn a_table_a_role_may_only_write_gets_the_write_and_no_type() {
10803 let cache = create_test_schema_cache();
10804 let names = crate::names::NameOverrides::parse(
10805 r#"{"tables": {"public.users": {"permissions":
10806 {"writer": {"insert": {"columns": "*", "check": {}}}}}}}"#,
10807 )
10808 .unwrap();
10809 let view = crate::role::cache_for_role(&cache, &names, "writer", false);
10810 let config = SchemaConfig {
10811 names: names.clone(),
10812 role: Some("writer".to_string()),
10813 ..SchemaConfig::default()
10814 };
10815 let generated = build_schema(&view, &config);
10816
10817 let schema = build_dynamic_schema(
10818 &generated,
10819 &view,
10820 None,
10821 None,
10822 Arc::new(names),
10823 std::time::Duration::from_secs(30),
10824 Some("writer"),
10825 )
10826 .expect("a role that may only write still has a buildable schema");
10827 let sdl = schema.sdl();
10828
10829 assert!(
10830 sdl.contains("insert_users("),
10831 "the write is there:\n{}",
10832 sdl
10833 );
10834 assert!(
10835 sdl.contains("users_insert_input"),
10836 "and its input:\n{}",
10837 sdl
10838 );
10839 assert!(!sdl.contains("type users "), "no row type:\n{}", sdl);
10841 assert!(!sdl.contains("insert_users_one"), "no insert_one:\n{}", sdl);
10842 assert!(
10843 !sdl.contains("returning"),
10844 "and nothing to return:\n{}",
10845 sdl
10846 );
10847 assert!(
10848 sdl.contains("users_mutation_response"),
10849 "the count is still answered:\n{}",
10850 sdl
10851 );
10852 assert!(
10853 !sdl.contains("_mutation_response_mutation_response"),
10854 "and no type is named twice over:\n{}",
10855 sdl
10856 );
10857 }
10858
10859 fn cache_with_a_mutation_function() -> SchemaCache {
10862 use postrust_core::schema_cache::{FuncVolatility, RetType, Routine};
10863
10864 let mut cache = create_test_schema_cache();
10865 let routine = Routine {
10866 schema: "public".into(),
10867 name: "add_to_score".into(),
10868 description: None,
10869 params: Vec::new(),
10870 return_type: RetType::SetOf("users".into()),
10871 returns_composite: true,
10872 volatility: FuncVolatility::Volatile,
10873 has_variadic: false,
10874 isolation_level: None,
10875 settings: Vec::new(),
10876 is_procedure: false,
10877 media_type: None,
10878 media_base_type: None,
10879 output_columns: Vec::new(),
10880 };
10881 cache
10882 .routines
10883 .insert(routine.qualified_identifier(), vec![routine]);
10884 cache
10885 }
10886
10887 #[test]
10892 fn a_mutation_function_needs_the_role_to_be_named() {
10893 let cache = cache_with_a_mutation_function();
10894 let withheld = crate::names::NameOverrides::parse(
10895 r#"{"tables": {"public.users": {"permissions":
10896 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10897 "functions": {"public.add_to_score": {"exposed_as": "mutation"}}}"#,
10898 )
10899 .unwrap();
10900 let granted = crate::names::NameOverrides::parse(
10901 r#"{"tables": {"public.users": {"permissions":
10902 {"reader": {"select": {"columns": "*", "filter": {}}}}}},
10903 "functions": {"public.add_to_score":
10904 {"exposed_as": "mutation", "roles": ["reader"]}}}"#,
10905 )
10906 .unwrap();
10907
10908 let sdl_for = |names: &crate::names::NameOverrides| {
10909 let view = crate::role::cache_for_role(&cache, names, "reader", false);
10910 let config = SchemaConfig {
10911 names: names.clone(),
10912 role: Some("reader".to_string()),
10913 ..SchemaConfig::default()
10914 };
10915 let generated = build_schema(&view, &config);
10916 build_dynamic_schema(
10917 &generated,
10918 &view,
10919 None,
10920 None,
10921 Arc::new(names.clone()),
10922 std::time::Duration::from_secs(30),
10923 Some("reader"),
10924 )
10925 .expect("schema builds")
10926 .sdl()
10927 };
10928
10929 let without = sdl_for(&withheld);
10930 assert!(
10931 !without.contains("add_to_score"),
10932 "not granted, so not there:\n{}",
10933 without
10934 );
10935 assert!(
10938 !without.contains("mutation: mutation_root"),
10939 "and no mutation root at all:\n{}",
10940 without
10941 );
10942
10943 let with = sdl_for(&granted);
10944 assert!(
10945 with.contains("add_to_score"),
10946 "named by a function permission, so it is:\n{}",
10947 with
10948 );
10949 }
10950
10951 #[test]
10953 fn a_role_the_document_never_names_is_granted_nothing() {
10954 let cache = Arc::new(create_test_schema_cache());
10955 let config = SchemaConfig {
10956 names: crate::names::NameOverrides::parse(
10957 r#"{"tables": {"public.users": {"permissions":
10958 {"reader": {"select": {"columns": "*", "filter": {}}}}}}}"#,
10959 )
10960 .unwrap(),
10961 ..SchemaConfig::default()
10962 };
10963 let empty = build_ungranted_schema(&cache, &config).expect("it builds");
10964 let sdl = empty.sdl();
10965 assert!(
10966 sdl.contains("no_queries_available"),
10967 "nothing to read:\n{}",
10968 sdl
10969 );
10970 assert!(!sdl.contains("type users "), "and no table:\n{}", sdl);
10971 assert!(build_ungranted_schema(&cache, &SchemaConfig::default()).is_none());
10973 }
10974
10975 #[test]
10978 fn a_column_a_role_may_set_without_seeing_is_in_the_input_alone() {
10979 let cache = create_test_schema_cache();
10980 let names = crate::names::NameOverrides::parse(
10981 r#"{"tables": {"public.users": {"permissions":
10982 {"user": {"select": {"columns": ["id"], "filter": {}},
10983 "insert": {"columns": "*", "check": {}}}}}}}"#,
10984 )
10985 .unwrap();
10986 let view = crate::role::cache_for_role(&cache, &names, "user", false);
10987 let config = SchemaConfig {
10988 names: names.clone(),
10989 role: Some("user".to_string()),
10990 ..SchemaConfig::default()
10991 };
10992 let generated = build_schema(&view, &config);
10993 let object = &generated.object_types["users"];
10994 assert_eq!(
10995 object
10996 .fields
10997 .iter()
10998 .map(|f| f.name.as_str())
10999 .collect::<Vec<_>>(),
11000 vec!["id"],
11001 "the type shows what may be read"
11002 );
11003 assert!(
11004 object.writable_fields.iter().any(|f| f.name == "name"),
11005 "and the input keeps what may be written"
11006 );
11007
11008 let schema = build_dynamic_schema(
11009 &generated,
11010 &view,
11011 None,
11012 None,
11013 Arc::new(names),
11014 std::time::Duration::from_secs(30),
11015 Some("user"),
11016 )
11017 .expect("schema builds");
11018 let sdl = schema.sdl();
11019 let input = sdl
11020 .split("input users_insert_input")
11021 .nth(1)
11022 .and_then(|rest| rest.split('}').next())
11023 .expect("the insert input is registered");
11024 assert!(input.contains("name:"), "settable without being readable");
11025 }
11026
11027 #[test]
11031 fn a_generated_column_is_readable_and_not_writable() {
11032 let mut cache = create_test_schema_cache();
11033 cache
11034 .tables
11035 .get_mut(&postrust_core::api_request::QualifiedIdentifier::new(
11036 "public", "users",
11037 ))
11038 .expect("the fixture has users")
11039 .columns
11040 .get_mut("id")
11041 .expect("with an id")
11042 .always_generated = true;
11043
11044 let names = crate::names::NameOverrides::default();
11045 let config = SchemaConfig {
11046 names: names.clone(),
11047 ..SchemaConfig::default()
11048 };
11049 let generated = build_schema(&cache, &config);
11050 let schema = build_dynamic_schema(
11051 &generated,
11052 &cache,
11053 None,
11054 None,
11055 Arc::new(names),
11056 std::time::Duration::from_secs(30),
11057 None,
11058 )
11059 .expect("schema builds");
11060 let sdl = schema.sdl();
11061
11062 let block = |header: &str| {
11063 sdl.split(header)
11064 .nth(1)
11065 .and_then(|rest| rest.split('}').next())
11066 .unwrap_or_else(|| panic!("{} is registered:\n{}", header, sdl))
11067 .to_string()
11068 };
11069 assert!(
11070 block("type users ").contains("id:"),
11071 "still readable:\n{}",
11072 sdl
11073 );
11074 for header in ["input users_insert_input", "input users_set_input"] {
11075 assert!(
11076 !block(header).contains("id:"),
11077 "{} may not name it:\n{}",
11078 header,
11079 block(header)
11080 );
11081 }
11082 assert!(
11085 !sdl.contains("users_inc_input"),
11086 "and nothing is left to increment:\n{}",
11087 sdl
11088 );
11089 assert!(
11090 block("input users_insert_input").contains("name:"),
11091 "and the rest of the table is untouched",
11092 );
11093 }
11094
11095 #[test]
11096 fn test_create_object_type() {
11097 let table = create_test_table("users");
11098 let obj = TableObjectType::from_table(&table);
11099 let _gql_obj = create_object_type(&obj, &[], None);
11100 }
11101
11102 #[test]
11103 fn test_create_query_type() {
11104 let cache = create_test_schema_cache();
11105 let config = SchemaConfig::default();
11106 let generated = build_schema(&cache, &config);
11107
11108 let _query = create_query_type(
11109 &generated,
11110 "query_root",
11111 None,
11112 Arc::new(HashMap::new()),
11113 Arc::new(Default::default()),
11114 );
11115 }
11116
11117 #[test]
11118 fn test_create_mutation_type() {
11119 let cache = create_test_schema_cache();
11120 let config = SchemaConfig::default();
11121 let generated = build_schema(&cache, &config);
11122
11123 let _mutation = create_mutation_type(
11124 &generated,
11125 "mutation_root",
11126 Arc::new(HashMap::new()),
11127 Arc::new(HashMap::new()),
11128 Arc::new(Default::default()),
11129 None,
11130 None,
11131 );
11132 }
11133
11134 #[test]
11139 fn a_scalar_is_named_the_way_a_client_declares_it() {
11140 use crate::types::{pg_type_to_graphql, GraphQLType};
11141
11142 assert_eq!(pg_type_to_graphql("jsonb").to_string(), "jsonb");
11147 assert_eq!(pg_type_to_graphql("json").to_string(), "json");
11148 assert_eq!(pg_type_to_graphql("int8").to_string(), "bigint");
11149 assert_eq!(pg_type_to_graphql("numeric").to_string(), "numeric");
11150 assert_eq!(pg_type_to_graphql("timestamptz").to_string(), "timestamptz");
11151 assert_eq!(pg_type_to_graphql("timestamp").to_string(), "timestamp");
11152 assert_eq!(pg_type_to_graphql("uuid").to_string(), "uuid");
11153
11154 assert_eq!(pg_type_to_graphql("geometry").to_string(), "geometry");
11157 assert_eq!(
11158 pg_type_to_graphql("colors_enum"),
11159 GraphQLType::Custom("colors_enum".to_string())
11160 );
11161
11162 assert_eq!(
11164 leaf_scalar_name(&pg_type_to_graphql("_text")),
11165 "String".to_string()
11166 );
11167 }
11168
11169 #[test]
11174 fn every_table_gets_a_boolean_expression() {
11175 let cache = create_test_schema_cache();
11176 let config = SchemaConfig::default();
11177 let generated = build_schema(&cache, &config);
11178
11179 let (inputs, _) = crate::input::bool_exp::build_inputs(
11180 &generated.object_types,
11181 &generated.relationship_fields,
11182 );
11183 let names: HashSet<String> = inputs.iter().map(|i| i.type_name().to_string()).collect();
11184
11185 for table in generated.object_types.keys() {
11186 let expected = format!("{}_bool_exp", table);
11187 assert!(
11188 names.contains(&expected),
11189 "no {} among {:?}",
11190 expected,
11191 names
11192 );
11193 }
11194 assert!(names.contains("String_comparison_exp"));
11196 assert!(names.contains("Int_comparison_exp"));
11197 }
11198
11199 #[test]
11200 fn a_schema_carrying_the_boolean_expressions_still_builds() {
11201 let cache = create_test_schema_cache();
11202 let config = SchemaConfig::default();
11203 let generated = build_schema(&cache, &config);
11204
11205 let schema = build_dynamic_schema(
11206 &generated,
11207 &cache,
11208 None,
11209 None,
11210 Arc::new(Default::default()),
11211 std::time::Duration::from_secs(30),
11212 None,
11213 );
11214 assert!(schema.is_ok(), "{:?}", schema.err());
11215
11216 let sdl = schema.unwrap().sdl();
11217 assert!(
11218 sdl.contains("_bool_exp"),
11219 "no boolean expressions in:\n{}",
11220 sdl
11221 );
11222 assert!(sdl.contains("_and"), "no _and in the generated expressions");
11223 }
11224
11225 #[test]
11230 fn test_build_schema_with_subscriptions() {
11231 let cache = create_test_schema_cache();
11232 let config = SchemaConfig {
11233 enable_subscriptions: true,
11234 ..SchemaConfig::default()
11235 };
11236 let generated = build_schema(&cache, &config);
11237
11238 let sub_fields = generate_subscription_fields(&cache, &generated);
11240 assert!(!sub_fields.is_empty(), "Should have subscription fields");
11241
11242 let result = build_dynamic_schema(
11244 &generated,
11245 &cache,
11246 Some(&sub_fields),
11247 None,
11248 Arc::new(Default::default()),
11249 std::time::Duration::from_secs(30),
11250 None,
11251 );
11252 assert!(result.is_ok(), "Schema with subscriptions should build");
11253 }
11254
11255 #[test]
11256 fn test_subscription_field_generation() {
11257 let cache = create_test_schema_cache();
11258 let config = SchemaConfig::default();
11259 let generated = build_schema(&cache, &config);
11260
11261 let fields = generate_subscription_fields(&cache, &generated);
11262
11263 assert_eq!(fields.len(), 1);
11265 assert_eq!(fields[0].name, "users");
11266 assert_eq!(fields[0].table_name, "users");
11267 assert_eq!(fields[0].channel_name(), "postrust_public_users");
11268 }
11269
11270 #[test]
11271 fn test_create_subscription_type() {
11272 let cache = create_test_schema_cache();
11273 let config = SchemaConfig::default();
11274 let generated = build_schema(&cache, &config);
11275 let _subscription = create_subscription_type(
11278 &generated,
11279 "subscription_root",
11280 None,
11281 Arc::new(Default::default()),
11282 Arc::new(Default::default()),
11283 std::time::Duration::from_secs(30),
11284 );
11285 }
11286}
11287
11288#[derive(Debug, PartialEq, Eq)]
11290enum PathStep {
11291 Key(String),
11292 Index(usize),
11293}
11294
11295fn walk_json_path(value: &Value, path: &str) -> Result<Value, async_graphql::Error> {
11302 let mut at = value;
11303 for step in parse_json_path(path)? {
11304 let next = match (&step, at) {
11305 (PathStep::Key(key), Value::Object(map)) => map.get(key.as_str()),
11306 (PathStep::Index(index), Value::List(items)) => items.get(*index),
11307 _ => None,
11310 };
11311 match next {
11312 Some(next) => at = next,
11313 None => return Ok(Value::Null),
11314 }
11315 }
11316 Ok(at.clone())
11317}
11318
11319fn unclosed_bracket(path: &str) -> async_graphql::Error {
11321 let message = format!("\"{}\" is not a JSON path: a bracket is never closed", path);
11322 validation_error(&message)
11323}
11324
11325fn parse_json_path(path: &str) -> Result<Vec<PathStep>, async_graphql::Error> {
11332 let mut steps = Vec::new();
11333 let mut chars = path.chars().peekable();
11334 if chars.peek() == Some(&'$') {
11336 chars.next();
11337 }
11338 while let Some(&c) = chars.peek() {
11339 match c {
11340 '.' => {
11342 chars.next();
11343 }
11344 '[' => {
11345 chars.next();
11346 let quote = match chars.peek() {
11347 Some(&q @ ('\'' | '"')) => {
11348 chars.next();
11349 Some(q)
11350 }
11351 _ => None,
11352 };
11353 let mut inner = String::new();
11354 loop {
11355 let Some(c) = chars.next() else {
11356 return Err(unclosed_bracket(path));
11357 };
11358 match (quote, c) {
11359 (Some(quote), c) if c == quote => break,
11360 (None, ']') => break,
11361 _ => inner.push(c),
11362 }
11363 }
11364 if quote.is_some() {
11366 match chars.next() {
11367 Some(']') => {}
11368 _ => return Err(unclosed_bracket(path)),
11369 }
11370 }
11371 match (quote, inner.parse::<usize>()) {
11374 (None, Ok(index)) => steps.push(PathStep::Index(index)),
11375 _ => steps.push(PathStep::Key(inner)),
11376 }
11377 }
11378 _ => {
11379 let mut key = String::new();
11380 while let Some(&c) = chars.peek() {
11381 if matches!(c, '.' | '[') {
11382 break;
11383 }
11384 key.push(c);
11385 chars.next();
11386 }
11387 steps.push(PathStep::Key(key));
11388 }
11389 }
11390 }
11391 Ok(steps)
11392}
11393
11394#[cfg(test)]
11395mod json_path_tests {
11396 use super::*;
11397
11398 fn steps(path: &str) -> Vec<PathStep> {
11399 parse_json_path(path).expect("the path parses")
11400 }
11401
11402 fn key(name: &str) -> PathStep {
11403 PathStep::Key(name.to_string())
11404 }
11405
11406 #[test]
11407 fn the_document_itself_has_no_steps() {
11408 assert_eq!(steps("$"), vec![]);
11409 assert_eq!(steps(""), vec![]);
11410 }
11411
11412 #[test]
11413 fn a_key_is_written_bare_or_after_a_dot() {
11414 assert_eq!(steps("a"), vec![key("a")]);
11415 assert_eq!(steps(".obj.c1"), vec![key("obj"), key("c1")]);
11416 assert_eq!(steps("._underscore"), vec![key("_underscore")]);
11417 }
11418
11419 #[test]
11420 fn a_bracket_holds_an_index_or_a_quoted_key() {
11421 assert_eq!(steps("arr[0]"), vec![key("arr"), PathStep::Index(0)]);
11422 assert_eq!(steps("['!@#$%^']"), vec![key("!@#$%^")]);
11423 assert_eq!(
11424 steps("translations['hello world!']"),
11425 vec![key("translations"), key("hello world!")]
11426 );
11427 }
11428
11429 #[test]
11431 fn a_dot_before_a_bracket_changes_nothing() {
11432 let expected = vec![key("objs"), PathStep::Index(0), key("\u{4f60}\u{597d}")];
11433 for spelling in [
11434 "objs[0]['\u{4f60}\u{597d}']",
11435 "objs[0][\"\u{4f60}\u{597d}\"]",
11436 "objs[0].[\"\u{4f60}\u{597d}\"]",
11437 "objs.[0].[\"\u{4f60}\u{597d}\"]",
11438 ] {
11439 assert_eq!(steps(spelling), expected, "{}", spelling);
11440 }
11441 }
11442
11443 #[test]
11444 fn a_bracket_that_never_closes_is_refused() {
11445 assert!(parse_json_path("arr[0").is_err());
11446 assert!(parse_json_path("['key'").is_err());
11447 }
11448
11449 #[test]
11450 fn a_step_the_document_does_not_have_reads_null() {
11451 let document = Value::from_json(serde_json::json!({
11452 "obj": {"c1": "c2"},
11453 "arr": [1, 2, 3],
11454 }))
11455 .expect("a document");
11456 let at = |path| walk_json_path(&document, path).expect("the path parses");
11457 assert_eq!(at(".obj.c1"), Value::String("c2".to_string()));
11458 assert_eq!(at("arr[1]"), Value::Number(2.into()));
11459 assert_eq!(at("arr[9]"), Value::Null);
11460 assert_eq!(at(".obj.nothing"), Value::Null);
11461 assert_eq!(at("obj[0]"), Value::Null);
11464 assert_eq!(at("arr.first"), Value::Null);
11465 }
11466}