Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,5 @@
- bugfix: Remove double NON_NULL wrapping on byPk argument types

## master

- bugfix: Reject queries that select a composite field without a subfield selection
6 changes: 6 additions & 0 deletions src/parser_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ where
T: Text<'a> + Eq + AsRef<str> + Clone,
T::Value: Hash,
{
if selection_set.items.is_empty() {
return Err(GraphQLError::validation(format!(
"Selection set must not be empty for type '{type_name}'"
)));
}

let mut selections: Vec<Field<'a, T>> = vec![];

for selection in &selection_set.items {
Expand Down
81 changes: 81 additions & 0 deletions test/expected/resolve_error_empty_selection_set.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
begin;
create table account(
id serial primary key,
parent_id int references account(id)
);
-- A composite field selected with no subfields must be rejected, not
-- silently resolved. See https://github.com/supabase/pg_graphql/issues/412
-- top level connection
select graphql.resolve($$
{
accountCollection
}
$$);
resolve
---------------------------------------------------------------------------------------------------------
{"data": null, "errors": [{"message": "Selection set must not be empty for type 'AccountConnection'"}]}
(1 row)

-- nested edges
select graphql.resolve($$
{
accountCollection {
edges
}
}
$$);
resolve
---------------------------------------------------------------------------------------------------
{"data": null, "errors": [{"message": "Selection set must not be empty for type 'AccountEdge'"}]}
(1 row)

-- nested node
select graphql.resolve($$
{
accountCollection {
edges {
node
}
}
}
$$);
resolve
-----------------------------------------------------------------------------------------------
{"data": null, "errors": [{"message": "Selection set must not be empty for type 'Account'"}]}
(1 row)

-- node by primary key
select graphql.resolve($$
{
accountByPk(id: 1)
}
$$);
resolve
-----------------------------------------------------------------------------------------------
{"data": null, "errors": [{"message": "Selection set must not be empty for type 'Account'"}]}
(1 row)

-- mutation payload
select graphql.resolve($$
mutation {
insertIntoAccountCollection(objects: [{ }])
}
$$);
resolve
-------------------------------------------------------------------------------------------------------------
{"data": null, "errors": [{"message": "Selection set must not be empty for type 'AccountInsertResponse'"}]}
(1 row)

-- a field skipped away at the top level still reports the empty operation
-- selection set, unchanged by the above
select graphql.resolve($$
{
accountCollection @skip(if: true)
}
$$);
resolve
--------------------------------------------------------------
{"errors": [{"message": "Selection set must not be empty"}]}
(1 row)

rollback;
60 changes: 60 additions & 0 deletions test/sql/resolve_error_empty_selection_set.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
begin;

create table account(
id serial primary key,
parent_id int references account(id)
);

-- A composite field selected with no subfields must be rejected, not
-- silently resolved. See https://github.com/supabase/pg_graphql/issues/412

-- top level connection
select graphql.resolve($$
{
accountCollection
}
$$);

-- nested edges
select graphql.resolve($$
{
accountCollection {
edges
}
}
$$);

-- nested node
select graphql.resolve($$
{
accountCollection {
edges {
node
}
}
}
$$);

-- node by primary key
select graphql.resolve($$
{
accountByPk(id: 1)
}
$$);

-- mutation payload
select graphql.resolve($$
mutation {
insertIntoAccountCollection(objects: [{ }])
}
$$);

-- a field skipped away at the top level still reports the empty operation
-- selection set, unchanged by the above
select graphql.resolve($$
{
accountCollection @skip(if: true)
}
$$);

rollback;