Skip to content

Commit 0d5b126

Browse files
authored
fix: fix incorrect coercion (#5491)
This is a pretty confusing behavior, but our choice of fields for `add_condition` doesn't actually affect the final m2m filter being generated, because it uses the linking table which isn't represented in `query_ast`, so this change does not change the actual filter. It only fixes a bug that occurs when we attempt to coerce the value being filtered against the column it's meant to filter. When we filter against an m2m table, for the coercion to work correctly we need to use `parent_field.linking_fields()`. [ORM-1124](https://linear.app/prisma-company/issue/ORM-1124/fix-conversion-failure-on-upsert-update-relationship) Fixes prisma/prisma#27452
1 parent c2b199a commit 0d5b126

3 files changed

Lines changed: 138 additions & 3 deletions

File tree

  • query-compiler/query-compiler/src/translate/query
  • query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions

query-compiler/query-compiler/src/translate/query/read.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,6 @@ fn build_read_related_records(
234234

235235
if let Some(results) = rrq.parent_results {
236236
let parent_link_id = rrq.parent_field.linking_fields();
237-
let child_link_id = rrq.parent_field.related_field().linking_fields();
238-
239237
let selection = results
240238
.into_iter()
241239
.exactly_one()
@@ -244,7 +242,19 @@ fn build_read_related_records(
244242
.pop()
245243
.unwrap();
246244

247-
for (field, val) in child_link_id
245+
// When we query for children by parent, we typically want to generate a query with filters
246+
// for every field in `parent_field.related_field().linking_fields()`. It's not correct to
247+
// do that for many-to-many relations though, because their `related_field` points at the
248+
// primary identifier of the child model, which cannot be used as a filter for the parent
249+
// identifiers. The actual field that must be used belongs to the linking table, and it
250+
// corresponds to the primary identifier of the parent model.
251+
let fields_to_filter_by = if rrq.parent_field.relation().is_many_to_many() {
252+
parent_link_id
253+
} else {
254+
rrq.parent_field.related_field().linking_fields()
255+
};
256+
257+
for (field, val) in fields_to_filter_by
248258
.assimilate(selection)
249259
.map_err(QueryGraphBuilderError::from)?
250260
.pairs

query-engine/connector-test-kit-rs/query-engine-tests/tests/new/regressions/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ mod prisma_22298;
3030
mod prisma_22971;
3131
mod prisma_24072;
3232
mod prisma_25290;
33+
mod prisma_27452;
3334
mod prisma_5952;
3435
mod prisma_6173;
3536
mod prisma_7010;
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use query_engine_tests::*;
2+
3+
#[test_suite(schema(schema), exclude(MongoDb, SqlServer))]
4+
mod prisma_27452 {
5+
fn schema() -> String {
6+
indoc! {
7+
r#"
8+
model User {
9+
id String @id @default(cuid())
10+
11+
posts Post[]
12+
comments Comment[]
13+
commentLikes CommentLike[]
14+
}
15+
16+
model Post {
17+
id String @id @default(cuid())
18+
19+
user User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
20+
ownerId String
21+
comments Comment[]
22+
commentLikes CommentLike[]
23+
24+
@@unique([id, ownerId])
25+
}
26+
27+
model Comment {
28+
id String @id @default(cuid())
29+
30+
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
31+
postId String
32+
user User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
33+
ownerId String
34+
commentLikes CommentLike[]
35+
}
36+
37+
model CommentLike {
38+
id Int @id @default(autoincrement())
39+
40+
user User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
41+
ownerId String
42+
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
43+
postId String
44+
comments Comment[]
45+
46+
@@unique([postId, ownerId])
47+
}
48+
"#
49+
}
50+
.to_string()
51+
}
52+
53+
#[connector_test]
54+
async fn comment_like_upsert_with_nested_comments(runner: Runner) -> TestResult<()> {
55+
let user_id = "1";
56+
let post_id = "1";
57+
let comment1_id = "1";
58+
let comment2_id = "2";
59+
60+
runner
61+
.query(&format!(
62+
r#"
63+
mutation {{
64+
createOneUser(data: {{
65+
id: "{user_id}"
66+
posts: {{
67+
createMany: {{
68+
data: [{{ id: "{post_id}" }}]
69+
}}
70+
}}
71+
comments: {{
72+
createMany: {{
73+
data: [
74+
{{ id: "{comment1_id}", postId: "{post_id}" }},
75+
{{ id: "{comment2_id}", postId: "{post_id}" }}
76+
]
77+
}}
78+
}}
79+
}}) {{
80+
id
81+
}}
82+
}}
83+
"#,
84+
))
85+
.await?;
86+
87+
let result = runner
88+
.query(&format!(
89+
r#"
90+
mutation {{
91+
upsertOneCommentLike(
92+
where: {{
93+
postId_ownerId: {{ postId: "{post_id}", ownerId: "{user_id}" }}
94+
}}
95+
create: {{
96+
postId: "{post_id}"
97+
ownerId: "{user_id}"
98+
comments: {{
99+
connect: [{{ id: "{comment1_id}" }}]
100+
}}
101+
}}
102+
update: {{
103+
comments: {{
104+
set: [{{ id: "{comment2_id}" }}]
105+
}}
106+
}}
107+
) {{
108+
comments {{
109+
id
110+
}}
111+
}}
112+
}}
113+
"#,
114+
))
115+
.await?;
116+
117+
insta::assert_snapshot!(
118+
result,
119+
@r###"{"data":{"upsertOneCommentLike":{"comments":[{"id":"1"}]}}}"###
120+
);
121+
122+
Ok(())
123+
}
124+
}

0 commit comments

Comments
 (0)