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