Skip to content

Commit 50ba2fd

Browse files
committed
perf(query-compiler): skip noop upsert updates
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
1 parent b99ebaa commit 50ba2fd

5 files changed

Lines changed: 252 additions & 20 deletions

File tree

query-compiler/core/src/query_graph_builder/write/upsert.rs

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::{write_args_parser::WriteArgsParser, *};
22
use crate::{
33
DataExpectation, ParsedField, ParsedInputMap, ParsedInputValue, ParsedObject, RowSink,
4-
inputs::{IfInput, RecordQueryFilterInput, UpdateRecordSelectorsInput},
4+
inputs::{IfInput, RecordQueryFilterInput, ReturnInput, UpdateRecordSelectorsInput},
55
query_ast::*,
66
query_graph::{Flow, QueryGraph, QueryGraphDependency},
77
};
@@ -101,14 +101,25 @@ pub(crate) fn upsert_record(
101101

102102
let create_node = create::create_record_node(graph, query_schema, model.clone(), create_argument)?;
103103

104-
let update_node = update::update_record_node(
105-
graph,
106-
query_schema,
107-
filter,
108-
model.clone(),
109-
update_argument,
110-
Some(&field),
111-
)?;
104+
let update_args = WriteArgsParser::from(&model, update_argument)?;
105+
let is_noop_update = update_args.args.is_empty();
106+
let update_node = if is_noop_update {
107+
let return_node = graph.create_node(Flow::Return(Vec::new()));
108+
109+
graph.create_edge(
110+
&read_parent_records_node,
111+
&return_node,
112+
QueryGraphDependency::ProjectedDataDependency(model_id.clone(), RowSink::All(&ReturnInput), None),
113+
)?;
114+
115+
for (relation_field, data_map) in update_args.nested {
116+
nested::connect_nested_query(graph, query_schema, return_node, relation_field, data_map)?;
117+
}
118+
119+
return_node
120+
} else {
121+
update::update_record_node_from_args(graph, query_schema, filter, model.clone(), update_args, Some(&field))?
122+
};
112123

113124
let read_node_create = graph.create_node(Query::Read(read_query.clone()));
114125
let read_node_update = graph.create_node(Query::Read(read_query));
@@ -130,7 +141,9 @@ pub(crate) fn upsert_record(
130141
// the update path (if the children already exists and goes to the THEN node).
131142
// It's only after we've executed the emulation that it'll traverse the update node, hence the ExecutionOrder between
132143
// the emulation node and the update node.
133-
if let Some(emulation_node) = utils::insert_emulated_on_update_with_intermediary_node(
144+
if is_noop_update {
145+
graph.create_edge(&if_node, &update_node, QueryGraphDependency::Then)?;
146+
} else if let Some(emulation_node) = utils::insert_emulated_on_update_with_intermediary_node(
134147
graph,
135148
query_schema,
136149
&model,
@@ -145,16 +158,18 @@ pub(crate) fn upsert_record(
145158

146159
graph.create_edge(&if_node, &create_node, QueryGraphDependency::Else)?;
147160

148-
// Pass-in the read parent record result to the update node RecordFilter to avoid a redundant read.
149-
graph.create_edge(
150-
&read_parent_records_node,
151-
&update_node,
152-
QueryGraphDependency::ProjectedDataDependency(
153-
model_id.clone(),
154-
RowSink::ExactlyOne(&UpdateRecordSelectorsInput),
155-
None,
156-
),
157-
)?;
161+
if !is_noop_update {
162+
// Pass-in the read parent record result to the update node RecordFilter to avoid a redundant read.
163+
graph.create_edge(
164+
&read_parent_records_node,
165+
&update_node,
166+
QueryGraphDependency::ProjectedDataDependency(
167+
model_id.clone(),
168+
RowSink::ExactlyOne(&UpdateRecordSelectorsInput),
169+
None,
170+
),
171+
)?;
172+
}
158173

159174
graph.create_edge(
160175
&update_node,
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"modelName": "User",
3+
"action": "upsertOne",
4+
"query": {
5+
"arguments": {
6+
"relationLoadStrategy": "query",
7+
"where": { "email": "user.1@prisma.io" },
8+
"update": {},
9+
"create": { "email": "user.1@prisma.io" }
10+
},
11+
"selection": {
12+
"$scalars": true
13+
}
14+
}
15+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"modelName": "User",
3+
"action": "upsertOne",
4+
"query": {
5+
"arguments": {
6+
"relationLoadStrategy": "query",
7+
"where": { "email": "user.1@prisma.io" },
8+
"update": {
9+
"posts": {
10+
"create": { "title": "Nested upsert update post" }
11+
}
12+
},
13+
"create": { "email": "user.1@prisma.io" }
14+
},
15+
"selection": {
16+
"$scalars": true,
17+
"posts": {
18+
"arguments": {},
19+
"selection": { "$scalars": true }
20+
}
21+
}
22+
}
23+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
source: query-compiler/query-compiler/tests/queries.rs
3+
expression: pretty
4+
input_file: query-compiler/query-compiler/tests/data/upsert-empty-update.json
5+
snapshot_kind: text
6+
---
7+
transaction
8+
dataMap {
9+
id: Int (id)
10+
email: String (email)
11+
role: Enum<Role> (role)
12+
}
13+
enums {
14+
Role: {
15+
admin: ADMIN
16+
user: USER
17+
}
18+
}
19+
let 0 = query «SELECT "public"."User"."id" FROM "public"."User" WHERE
20+
"public"."User"."email" = $1 OFFSET $2»
21+
params [const(String("user.1@prisma.io")), const(BigInt(0))]
22+
in if (rowCountNeq 0 (get 0))
23+
then let 2 = get 0
24+
in let 2 = unique (validate (get 2)
25+
[ rowCountNeq 0
26+
] orRaise "MISSING_RECORD");
27+
2$id = mapField id (get 2)
28+
in unique (query «SELECT "public"."User"."id",
29+
"public"."User"."email",
30+
"public"."User"."role"::text FROM
31+
"public"."User" WHERE "public"."User"."id" = $1
32+
LIMIT $2 OFFSET $3»
33+
params [var(2$id as Int), const(BigInt(1)),
34+
const(BigInt(0))])
35+
else let 1 = unique (query «INSERT INTO "public"."User" ("email","role")
36+
VALUES ($1,CAST($2::text AS "public"."Role"))
37+
RETURNING "public"."User"."id"»
38+
params [const(String("user.1@prisma.io")),
39+
const(String("user"))])
40+
in let 1 = unique (validate (get 1)
41+
[ rowCountNeq 0
42+
] orRaise "MISSING_RECORD");
43+
1$id = mapField id (get 1)
44+
in unique (query «SELECT "public"."User"."id",
45+
"public"."User"."email",
46+
"public"."User"."role"::text FROM
47+
"public"."User" WHERE "public"."User"."id" = $1
48+
LIMIT $2 OFFSET $3»
49+
params [var(1$id as Int), const(BigInt(1)),
50+
const(BigInt(0))])
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
---
2+
source: query-compiler/query-compiler/tests/queries.rs
3+
expression: pretty
4+
input_file: query-compiler/query-compiler/tests/data/upsert-nested-only-update.json
5+
snapshot_kind: text
6+
---
7+
transaction
8+
dataMap {
9+
id: Int (id)
10+
email: String (email)
11+
role: Enum<Role> (role)
12+
posts (from @nested$posts): {
13+
id: Int (id)
14+
title: String (title)
15+
userId: Int (userId)
16+
}
17+
}
18+
enums {
19+
Role: {
20+
admin: ADMIN
21+
user: USER
22+
}
23+
}
24+
let 0 = query «SELECT "public"."User"."id" FROM "public"."User" WHERE
25+
"public"."User"."email" = $1 OFFSET $2»
26+
params [const(String("user.1@prisma.io")), const(BigInt(0))]
27+
in if (rowCountNeq 0 (get 0))
28+
then let 2 = get 0
29+
in let 2 = unique (validate (get 2)
30+
[ rowCountNeq 0
31+
] orRaise "MISSING_RELATED_RECORD");
32+
2$id = mapField id (get 2)
33+
in unique (query «INSERT INTO "public"."Post" ("title","userId")
34+
VALUES ($1,$2) RETURNING "public"."Post"."id"»
35+
params [const(String("Nested upsert update post")),
36+
var(2$id as Int)]);
37+
let 2 = unique (validate (get 2)
38+
[ rowCountNeq 0
39+
] orRaise "MISSING_RECORD");
40+
2$id = mapField id (get 2)
41+
in rawNestedReadUnique (query «SELECT "public"."User"."id",
42+
"public"."User"."email",
43+
"public"."User"."role"::text FROM
44+
"public"."User" WHERE
45+
"public"."User"."id" = $1 LIMIT $2
46+
OFFSET $3»
47+
params [var(2$id as Int),
48+
const(BigInt(1)),
49+
const(BigInt(0))]
50+
fields {
51+
id: 0
52+
email: 1
53+
role: 2
54+
}
55+
relations [posts on left.0 = right.2 many (query
56+
«SELECT
57+
"public"."Post"."id",
58+
"public"."Post"."title",
59+
"public"."Post"."userId"
60+
FROM
61+
"public"."Post"
62+
WHERE
63+
"public"."Post"."userId"
64+
=
65+
$1
66+
OFFSET
67+
$2»
68+
params [var(@parent$id as Int),
69+
const(BigInt(0))]
70+
fields {
71+
id: 0
72+
title: 1
73+
userId: 2
74+
})])
75+
enums {
76+
Role: {
77+
admin: ADMIN
78+
user: USER
79+
}
80+
}
81+
else let 1 = unique (query «INSERT INTO "public"."User" ("email","role")
82+
VALUES ($1,CAST($2::text AS "public"."Role"))
83+
RETURNING "public"."User"."id"»
84+
params [const(String("user.1@prisma.io")),
85+
const(String("user"))])
86+
in let 1 = unique (validate (get 1)
87+
[ rowCountNeq 0
88+
] orRaise "MISSING_RECORD");
89+
1$id = mapField id (get 1)
90+
in rawNestedReadUnique (query «SELECT "public"."User"."id",
91+
"public"."User"."email",
92+
"public"."User"."role"::text FROM
93+
"public"."User" WHERE
94+
"public"."User"."id" = $1 LIMIT $2
95+
OFFSET $3»
96+
params [var(1$id as Int),
97+
const(BigInt(1)),
98+
const(BigInt(0))]
99+
fields {
100+
id: 0
101+
email: 1
102+
role: 2
103+
}
104+
relations [posts on left.0 = right.2 many (query
105+
«SELECT
106+
"public"."Post"."id",
107+
"public"."Post"."title",
108+
"public"."Post"."userId"
109+
FROM
110+
"public"."Post"
111+
WHERE
112+
"public"."Post"."userId"
113+
=
114+
$1
115+
OFFSET
116+
$2»
117+
params [var(@parent$id as Int),
118+
const(BigInt(0))]
119+
fields {
120+
id: 0
121+
title: 1
122+
userId: 2
123+
})])
124+
enums {
125+
Role: {
126+
admin: ADMIN
127+
user: USER
128+
}
129+
}

0 commit comments

Comments
 (0)