Skip to content

Commit 7d21ada

Browse files
authored
Ignore when required variable was already added to selection (#6995)
1 parent a869df6 commit 7d21ada

24 files changed

+827
-33
lines changed

src/HotChocolate/Fusion/src/Core/Planning/Pipeline/FieldRequirementsPlannerMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ private static void ResolveVariablesInContext(
302302
out var providingExecutionStep))
303303
{
304304
executionStep.DependsOn.Add(providingExecutionStep);
305-
executionStep.Variables.Add(requirement, stateKey);
305+
executionStep.Variables.TryAdd(requirement, stateKey);
306306
}
307307
}
308308

src/HotChocolate/Fusion/test/Composition.Tests/RequireTests.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,9 @@
55

66
namespace HotChocolate.Fusion.Composition;
77

8-
public class RequireTests
8+
public class RequireTests(ITestOutputHelper output)
99
{
10-
private readonly Func<ICompositionLog> _logFactory;
11-
12-
public RequireTests(ITestOutputHelper output)
13-
{
14-
_logFactory = () => new TestCompositionLog(output);
15-
}
10+
private readonly Func<ICompositionLog> _logFactory = () => new TestCompositionLog(output);
1611

1712
[Fact]
1813
public async Task Require_Scalar_Arguments_No_Overloads()

src/HotChocolate/Fusion/test/Composition.Tests/__snapshots__/DemoIntegrationTests.Accounts_And_Reviews2_Products_With_Nodes.graphql

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ schema
88
@transport(subgraph: "Products", location: "ws:\/\/localhost:5000\/graphql", kind: "WebSocket")
99
@node(subgraph: "Accounts", types: [ "User" ])
1010
@node(subgraph: "Reviews2", types: [ "User", "Review" ])
11-
@node(subgraph: "Products", types: [ "Product" ]) {
11+
@node(subgraph: "Products", types: [ "ProductConfiguration", "Product", "ProductBookmark" ]) {
1212
query: Query
1313
mutation: Mutation
1414
subscription: Subscription
@@ -33,11 +33,17 @@ type Query {
3333
@resolver(subgraph: "Reviews2", select: "{ nodes(ids: $ids) }", arguments: [ { name: "ids", type: "[ID!]!" } ])
3434
@variable(subgraph: "Products", name: "ids", argument: "ids")
3535
@resolver(subgraph: "Products", select: "{ nodes(ids: $ids) }", arguments: [ { name: "ids", type: "[ID!]!" } ])
36+
productBookmarkByUsername(username: ID!): ProductBookmark
37+
@variable(subgraph: "Products", name: "username", argument: "username")
38+
@resolver(subgraph: "Products", select: "{ productBookmarkByUsername(username: $username) }", arguments: [ { name: "username", type: "ID!" } ])
3639
productById(id: ID!): Product
3740
@variable(subgraph: "Reviews2", name: "id", argument: "id")
3841
@resolver(subgraph: "Reviews2", select: "{ productById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
3942
@variable(subgraph: "Products", name: "id", argument: "id")
4043
@resolver(subgraph: "Products", select: "{ productById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
44+
productConfigurationByUsername(username: ID!): ProductConfiguration
45+
@variable(subgraph: "Products", name: "username", argument: "username")
46+
@resolver(subgraph: "Products", select: "{ productConfigurationByUsername(username: $username) }", arguments: [ { name: "username", type: "ID!" } ])
4147
reviewById(id: ID!): Review
4248
@variable(subgraph: "Reviews2", name: "id", argument: "id")
4349
@resolver(subgraph: "Reviews2", select: "{ reviewById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
@@ -120,6 +126,34 @@ type Product implements Node
120126
@source(subgraph: "Products")
121127
}
122128

129+
type ProductBookmark implements Node
130+
@variable(subgraph: "Products", name: "ProductBookmark_id", select: "id")
131+
@resolver(subgraph: "Products", select: "{ node(id: $ProductBookmark_id) { ... on ProductBookmark { ... ProductBookmark } } }", arguments: [ { name: "ProductBookmark_id", type: "ID!" } ])
132+
@resolver(subgraph: "Products", select: "{ nodes(ids: $ProductBookmark_id) { ... on ProductBookmark { ... ProductBookmark } } }", arguments: [ { name: "ProductBookmark_id", type: "[ID!]!" } ], kind: "BATCH") {
133+
id: ID!
134+
@source(subgraph: "Products")
135+
note: String!
136+
@source(subgraph: "Products")
137+
productId: Int!
138+
@source(subgraph: "Products")
139+
username: String!
140+
@source(subgraph: "Products")
141+
}
142+
143+
type ProductConfiguration implements Node
144+
@variable(subgraph: "Products", name: "ProductConfiguration_id", select: "id")
145+
@resolver(subgraph: "Products", select: "{ node(id: $ProductConfiguration_id) { ... on ProductConfiguration { ... ProductConfiguration } } }", arguments: [ { name: "ProductConfiguration_id", type: "ID!" } ])
146+
@resolver(subgraph: "Products", select: "{ nodes(ids: $ProductConfiguration_id) { ... on ProductConfiguration { ... ProductConfiguration } } }", arguments: [ { name: "ProductConfiguration_id", type: "[ID!]!" } ], kind: "BATCH") {
147+
configurationName: String!
148+
@source(subgraph: "Products")
149+
id: ID!
150+
@source(subgraph: "Products")
151+
productId: Int!
152+
@source(subgraph: "Products")
153+
username: String!
154+
@source(subgraph: "Products")
155+
}
156+
123157
type ProductDimension {
124158
size: Int!
125159
@source(subgraph: "Products")
@@ -193,6 +227,14 @@ type User implements Node
193227
name: String!
194228
@source(subgraph: "Accounts")
195229
@source(subgraph: "Reviews2")
230+
productBookmarkByUsername: ProductBookmark
231+
@source(subgraph: "Products")
232+
@variable(subgraph: "Accounts", name: "User_username", select: "username")
233+
@resolver(subgraph: "Products", select: "{ productBookmarkByUsername(username: $User_username) }", arguments: [ { name: "User_username", type: "String!" } ])
234+
productConfigurationByUsername: ProductConfiguration
235+
@source(subgraph: "Products")
236+
@variable(subgraph: "Accounts", name: "User_username", select: "username")
237+
@resolver(subgraph: "Products", select: "{ productConfigurationByUsername(username: $User_username) }", arguments: [ { name: "User_username", type: "String!" } ])
196238
reviews: [Review!]!
197239
@source(subgraph: "Reviews2")
198240
username: String!

src/HotChocolate/Fusion/test/Composition.Tests/__snapshots__/DemoIntegrationTests.Accounts_And_Reviews_Products.graphql

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,17 @@ schema
1414
type Query {
1515
errorField: String
1616
@resolver(subgraph: "Accounts", select: "{ errorField }")
17+
productBookmarkByUsername(username: ID!): ProductBookmark
18+
@variable(subgraph: "Products", name: "username", argument: "username")
19+
@resolver(subgraph: "Products", select: "{ productBookmarkByUsername(username: $username) }", arguments: [ { name: "username", type: "ID!" } ])
1720
productById(id: ID!): Product
1821
@variable(subgraph: "Reviews", name: "id", argument: "id")
1922
@resolver(subgraph: "Reviews", select: "{ productById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
2023
@variable(subgraph: "Products", name: "id", argument: "id")
2124
@resolver(subgraph: "Products", select: "{ productById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
25+
productConfigurationByUsername(username: ID!): ProductConfiguration
26+
@variable(subgraph: "Products", name: "username", argument: "username")
27+
@resolver(subgraph: "Products", select: "{ productConfigurationByUsername(username: $username) }", arguments: [ { name: "username", type: "ID!" } ])
2228
reviewById(id: ID!): Review
2329
@variable(subgraph: "Reviews", name: "id", argument: "id")
2430
@resolver(subgraph: "Reviews", select: "{ reviewById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
@@ -102,6 +108,34 @@ type Product implements Node
102108
@source(subgraph: "Products")
103109
}
104110

111+
type ProductBookmark implements Node
112+
@variable(subgraph: "Products", name: "ProductBookmark_id", select: "id")
113+
@resolver(subgraph: "Products", select: "{ node(id: $ProductBookmark_id) { ... on ProductBookmark { ... ProductBookmark } } }", arguments: [ { name: "ProductBookmark_id", type: "ID!" } ])
114+
@resolver(subgraph: "Products", select: "{ nodes(ids: $ProductBookmark_id) { ... on ProductBookmark { ... ProductBookmark } } }", arguments: [ { name: "ProductBookmark_id", type: "[ID!]!" } ], kind: "BATCH") {
115+
id: ID!
116+
@source(subgraph: "Products")
117+
note: String!
118+
@source(subgraph: "Products")
119+
productId: Int!
120+
@source(subgraph: "Products")
121+
username: String!
122+
@source(subgraph: "Products")
123+
}
124+
125+
type ProductConfiguration implements Node
126+
@variable(subgraph: "Products", name: "ProductConfiguration_id", select: "id")
127+
@resolver(subgraph: "Products", select: "{ node(id: $ProductConfiguration_id) { ... on ProductConfiguration { ... ProductConfiguration } } }", arguments: [ { name: "ProductConfiguration_id", type: "ID!" } ])
128+
@resolver(subgraph: "Products", select: "{ nodes(ids: $ProductConfiguration_id) { ... on ProductConfiguration { ... ProductConfiguration } } }", arguments: [ { name: "ProductConfiguration_id", type: "[ID!]!" } ], kind: "BATCH") {
129+
configurationName: String!
130+
@source(subgraph: "Products")
131+
id: ID!
132+
@source(subgraph: "Products")
133+
productId: Int!
134+
@source(subgraph: "Products")
135+
username: String!
136+
@source(subgraph: "Products")
137+
}
138+
105139
type ProductDimension {
106140
size: Int!
107141
@source(subgraph: "Products")
@@ -171,6 +205,14 @@ type User implements Node
171205
name: String!
172206
@source(subgraph: "Accounts")
173207
@source(subgraph: "Reviews")
208+
productBookmarkByUsername: ProductBookmark
209+
@source(subgraph: "Products")
210+
@variable(subgraph: "Accounts", name: "User_username", select: "username")
211+
@resolver(subgraph: "Products", select: "{ productBookmarkByUsername(username: $User_username) }", arguments: [ { name: "User_username", type: "String!" } ])
212+
productConfigurationByUsername: ProductConfiguration
213+
@source(subgraph: "Products")
214+
@variable(subgraph: "Accounts", name: "User_username", select: "username")
215+
@resolver(subgraph: "Products", select: "{ productConfigurationByUsername(username: $User_username) }", arguments: [ { name: "User_username", type: "String!" } ])
174216
reviews: [Review!]!
175217
@source(subgraph: "Reviews")
176218
username: String!

src/HotChocolate/Fusion/test/Composition.Tests/__snapshots__/DemoIntegrationTests.Accounts_And_Reviews_Products_AutoCompose_With_Node.graphql

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,17 @@ type Query {
1717
@resolver(subgraph: "Reviews", select: "{ authorById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
1818
errorField: String
1919
@resolver(subgraph: "Accounts", select: "{ errorField }")
20+
productBookmarkByUsername(username: ID!): ProductBookmark
21+
@variable(subgraph: "Products", name: "username", argument: "username")
22+
@resolver(subgraph: "Products", select: "{ productBookmarkByUsername(username: $username) }", arguments: [ { name: "username", type: "ID!" } ])
2023
productById(id: ID!): Product
2124
@variable(subgraph: "Reviews", name: "id", argument: "id")
2225
@resolver(subgraph: "Reviews", select: "{ productById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
2326
@variable(subgraph: "Products", name: "id", argument: "id")
2427
@resolver(subgraph: "Products", select: "{ productById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
28+
productConfigurationByUsername(username: ID!): ProductConfiguration
29+
@variable(subgraph: "Products", name: "username", argument: "username")
30+
@resolver(subgraph: "Products", select: "{ productConfigurationByUsername(username: $username) }", arguments: [ { name: "username", type: "ID!" } ])
2531
reviewById(id: ID!): Review
2632
@variable(subgraph: "Reviews", name: "id", argument: "id")
2733
@resolver(subgraph: "Reviews", select: "{ reviewById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
@@ -115,6 +121,34 @@ type Product implements Node
115121
@source(subgraph: "Products")
116122
}
117123

124+
type ProductBookmark implements Node
125+
@variable(subgraph: "Products", name: "ProductBookmark_id", select: "id")
126+
@resolver(subgraph: "Products", select: "{ node(id: $ProductBookmark_id) { ... on ProductBookmark { ... ProductBookmark } } }", arguments: [ { name: "ProductBookmark_id", type: "ID!" } ])
127+
@resolver(subgraph: "Products", select: "{ nodes(ids: $ProductBookmark_id) { ... on ProductBookmark { ... ProductBookmark } } }", arguments: [ { name: "ProductBookmark_id", type: "[ID!]!" } ], kind: "BATCH") {
128+
id: ID!
129+
@source(subgraph: "Products")
130+
note: String!
131+
@source(subgraph: "Products")
132+
productId: Int!
133+
@source(subgraph: "Products")
134+
username: String!
135+
@source(subgraph: "Products")
136+
}
137+
138+
type ProductConfiguration implements Node
139+
@variable(subgraph: "Products", name: "ProductConfiguration_id", select: "id")
140+
@resolver(subgraph: "Products", select: "{ node(id: $ProductConfiguration_id) { ... on ProductConfiguration { ... ProductConfiguration } } }", arguments: [ { name: "ProductConfiguration_id", type: "ID!" } ])
141+
@resolver(subgraph: "Products", select: "{ nodes(ids: $ProductConfiguration_id) { ... on ProductConfiguration { ... ProductConfiguration } } }", arguments: [ { name: "ProductConfiguration_id", type: "[ID!]!" } ], kind: "BATCH") {
142+
configurationName: String!
143+
@source(subgraph: "Products")
144+
id: ID!
145+
@source(subgraph: "Products")
146+
productId: Int!
147+
@source(subgraph: "Products")
148+
username: String!
149+
@source(subgraph: "Products")
150+
}
151+
118152
type ProductDimension {
119153
size: Int!
120154
@source(subgraph: "Products")

src/HotChocolate/Fusion/test/Composition.Tests/__snapshots__/DemoIntegrationTests.Accounts_And_Reviews_Products_With_Nodes.graphql

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ schema
88
@transport(subgraph: "Products", location: "ws:\/\/localhost:5000\/graphql", kind: "WebSocket")
99
@node(subgraph: "Accounts", types: [ "User" ])
1010
@node(subgraph: "Reviews", types: [ "User", "Review" ])
11-
@node(subgraph: "Products", types: [ "Product" ]) {
11+
@node(subgraph: "Products", types: [ "ProductConfiguration", "Product", "ProductBookmark" ]) {
1212
query: Query
1313
mutation: Mutation
1414
subscription: Subscription
@@ -33,11 +33,17 @@ type Query {
3333
@resolver(subgraph: "Reviews", select: "{ nodes(ids: $ids) }", arguments: [ { name: "ids", type: "[ID!]!" } ])
3434
@variable(subgraph: "Products", name: "ids", argument: "ids")
3535
@resolver(subgraph: "Products", select: "{ nodes(ids: $ids) }", arguments: [ { name: "ids", type: "[ID!]!" } ])
36+
productBookmarkByUsername(username: ID!): ProductBookmark
37+
@variable(subgraph: "Products", name: "username", argument: "username")
38+
@resolver(subgraph: "Products", select: "{ productBookmarkByUsername(username: $username) }", arguments: [ { name: "username", type: "ID!" } ])
3639
productById(id: ID!): Product
3740
@variable(subgraph: "Reviews", name: "id", argument: "id")
3841
@resolver(subgraph: "Reviews", select: "{ productById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
3942
@variable(subgraph: "Products", name: "id", argument: "id")
4043
@resolver(subgraph: "Products", select: "{ productById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
44+
productConfigurationByUsername(username: ID!): ProductConfiguration
45+
@variable(subgraph: "Products", name: "username", argument: "username")
46+
@resolver(subgraph: "Products", select: "{ productConfigurationByUsername(username: $username) }", arguments: [ { name: "username", type: "ID!" } ])
4147
reviewById(id: ID!): Review
4248
@variable(subgraph: "Reviews", name: "id", argument: "id")
4349
@resolver(subgraph: "Reviews", select: "{ reviewById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
@@ -121,6 +127,34 @@ type Product implements Node
121127
@source(subgraph: "Products")
122128
}
123129

130+
type ProductBookmark implements Node
131+
@variable(subgraph: "Products", name: "ProductBookmark_id", select: "id")
132+
@resolver(subgraph: "Products", select: "{ node(id: $ProductBookmark_id) { ... on ProductBookmark { ... ProductBookmark } } }", arguments: [ { name: "ProductBookmark_id", type: "ID!" } ])
133+
@resolver(subgraph: "Products", select: "{ nodes(ids: $ProductBookmark_id) { ... on ProductBookmark { ... ProductBookmark } } }", arguments: [ { name: "ProductBookmark_id", type: "[ID!]!" } ], kind: "BATCH") {
134+
id: ID!
135+
@source(subgraph: "Products")
136+
note: String!
137+
@source(subgraph: "Products")
138+
productId: Int!
139+
@source(subgraph: "Products")
140+
username: String!
141+
@source(subgraph: "Products")
142+
}
143+
144+
type ProductConfiguration implements Node
145+
@variable(subgraph: "Products", name: "ProductConfiguration_id", select: "id")
146+
@resolver(subgraph: "Products", select: "{ node(id: $ProductConfiguration_id) { ... on ProductConfiguration { ... ProductConfiguration } } }", arguments: [ { name: "ProductConfiguration_id", type: "ID!" } ])
147+
@resolver(subgraph: "Products", select: "{ nodes(ids: $ProductConfiguration_id) { ... on ProductConfiguration { ... ProductConfiguration } } }", arguments: [ { name: "ProductConfiguration_id", type: "[ID!]!" } ], kind: "BATCH") {
148+
configurationName: String!
149+
@source(subgraph: "Products")
150+
id: ID!
151+
@source(subgraph: "Products")
152+
productId: Int!
153+
@source(subgraph: "Products")
154+
username: String!
155+
@source(subgraph: "Products")
156+
}
157+
124158
type ProductDimension {
125159
size: Int!
126160
@source(subgraph: "Products")
@@ -191,6 +225,14 @@ type User implements Node
191225
name: String!
192226
@source(subgraph: "Accounts")
193227
@source(subgraph: "Reviews")
228+
productBookmarkByUsername: ProductBookmark
229+
@source(subgraph: "Products")
230+
@variable(subgraph: "Accounts", name: "User_username", select: "username")
231+
@resolver(subgraph: "Products", select: "{ productBookmarkByUsername(username: $User_username) }", arguments: [ { name: "User_username", type: "String!" } ])
232+
productConfigurationByUsername: ProductConfiguration
233+
@source(subgraph: "Products")
234+
@variable(subgraph: "Accounts", name: "User_username", select: "username")
235+
@resolver(subgraph: "Products", select: "{ productConfigurationByUsername(username: $User_username) }", arguments: [ { name: "User_username", type: "String!" } ])
194236
reviews: [Review!]!
195237
@source(subgraph: "Reviews")
196238
username: String!

src/HotChocolate/Fusion/test/Composition.Tests/__snapshots__/RequireTests.Require_Scalar_Arguments_No_Overloads.graphql

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,19 @@ type Query {
1919
@resolver(subgraph: "Reviews", select: "{ authorById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
2020
errorField: String
2121
@resolver(subgraph: "Accounts", select: "{ errorField }")
22+
productBookmarkByUsername(username: ID!): ProductBookmark
23+
@variable(subgraph: "Products", name: "username", argument: "username")
24+
@resolver(subgraph: "Products", select: "{ productBookmarkByUsername(username: $username) }", arguments: [ { name: "username", type: "ID!" } ])
2225
productById(id: ID!): Product
2326
@variable(subgraph: "Reviews", name: "id", argument: "id")
2427
@resolver(subgraph: "Reviews", select: "{ productById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
2528
@variable(subgraph: "Products", name: "id", argument: "id")
2629
@resolver(subgraph: "Products", select: "{ productById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
2730
@variable(subgraph: "Shipping", name: "id", argument: "id")
2831
@resolver(subgraph: "Shipping", select: "{ productById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
32+
productConfigurationByUsername(username: ID!): ProductConfiguration
33+
@variable(subgraph: "Products", name: "username", argument: "username")
34+
@resolver(subgraph: "Products", select: "{ productConfigurationByUsername(username: $username) }", arguments: [ { name: "username", type: "ID!" } ])
2935
reviewById(id: ID!): Review
3036
@variable(subgraph: "Reviews", name: "id", argument: "id")
3137
@resolver(subgraph: "Reviews", select: "{ reviewById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
@@ -135,6 +141,34 @@ type Product implements Node
135141
@source(subgraph: "Products")
136142
}
137143

144+
type ProductBookmark implements Node
145+
@variable(subgraph: "Products", name: "ProductBookmark_id", select: "id")
146+
@resolver(subgraph: "Products", select: "{ node(id: $ProductBookmark_id) { ... on ProductBookmark { ... ProductBookmark } } }", arguments: [ { name: "ProductBookmark_id", type: "ID!" } ])
147+
@resolver(subgraph: "Products", select: "{ nodes(ids: $ProductBookmark_id) { ... on ProductBookmark { ... ProductBookmark } } }", arguments: [ { name: "ProductBookmark_id", type: "[ID!]!" } ], kind: "BATCH") {
148+
id: ID!
149+
@source(subgraph: "Products")
150+
note: String!
151+
@source(subgraph: "Products")
152+
productId: Int!
153+
@source(subgraph: "Products")
154+
username: String!
155+
@source(subgraph: "Products")
156+
}
157+
158+
type ProductConfiguration implements Node
159+
@variable(subgraph: "Products", name: "ProductConfiguration_id", select: "id")
160+
@resolver(subgraph: "Products", select: "{ node(id: $ProductConfiguration_id) { ... on ProductConfiguration { ... ProductConfiguration } } }", arguments: [ { name: "ProductConfiguration_id", type: "ID!" } ])
161+
@resolver(subgraph: "Products", select: "{ nodes(ids: $ProductConfiguration_id) { ... on ProductConfiguration { ... ProductConfiguration } } }", arguments: [ { name: "ProductConfiguration_id", type: "[ID!]!" } ], kind: "BATCH") {
162+
configurationName: String!
163+
@source(subgraph: "Products")
164+
id: ID!
165+
@source(subgraph: "Products")
166+
productId: Int!
167+
@source(subgraph: "Products")
168+
username: String!
169+
@source(subgraph: "Products")
170+
}
171+
138172
type ProductDimension {
139173
size: Int!
140174
@source(subgraph: "Products")

0 commit comments

Comments
 (0)