-
Notifications
You must be signed in to change notification settings - Fork 999
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(graph,graphql): filter by child entity (interfaces) #3677
Conversation
3be9131
to
15ffb98
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good in general; surprising that it was this easy! I am a little worried about using or
in queries, but we can look more into this once we have some examples of queries that use this feature and are slow and try out alternative queries.
The tests should be adapted a little, but other than that, looks good!
graphql/tests/query.rs
Outdated
@@ -154,6 +163,7 @@ fn test_schema(id: DeploymentHash, id_type: IdType) -> Schema { | |||
id: ID! | |||
name: String! | |||
members: [Musician!]! @derivedFrom(field: \"bands\") | |||
reviews: [BandReview] @derivedFrom(field: \"band\") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason why this can't be [BandReview!]!
like the other fields? IIRC, we do not allow arrays with nulls in them anyway. (There's a couple more places here that raise the same question)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those are now non-nullable
bandReviews: [BandReview] @derivedFrom(field: \"author\") | ||
songReviews: [SongReview] @derivedFrom(field: \"author\") | ||
reviews: [Review] @derivedFrom(field: \"author\") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like all the interface fields in these tests are @derivedFrom
. Could some of them be made into direct fields? In general, we want to make sure that all four combinations of derived/direct and single value/array fields work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a few new test scenarios but I don't know exactly how the interfaces are supposed to work, what is possible, and what is not. I'm guessing here a bit. One test is failing but it yells at me about some non-existing operator (operator does not exist: bytea = text
). I'm lost at this point :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That error means that somewhere where a SQL query compares two columns with =
, one is of type bytea
and the other of type text
. That's an error somewhere in query generation, not necessarily connected to child filters. I wanted to try that out myself, but couldn't build this branch since cargo
gets unhappy when it tries to check out https://github.com/dotansimha/graphql-tools-rs?branch=clone-validation-error#89085f31
and says
object not found - no match for id (89085f316a71b390fecd9bd4c3f55c3884c33169); class=Odb (9); code=NotFound (-3)
The best way to get to the bottom of this is to turn on logging of all statements in Postgres with alter system set log_statement = 'all';
in psql
in your test database. (You might have to restart Postgres after this) With this set, run the failing test and the Postgres logs will have the query that is causing that error. Actually, I just noticed that the error message from the test has the full query, though I still recommend setting this.
15ffb98
to
fdee4db
Compare
I don't know exactly how the interfaces are supposed to work, what is possible, and what is not with and without the |
I just made a big table to get an overview of what combinations of parent and child types are possible and came up with this:
They all are for a query along the lines of
The columns mean:
I tried to fill this in with examples that I see possible from the test schema and marked the ones where I think that's not possible in the schema with |
@lutter the table says that For this query: query {
songs(
first: 100,
orderBy: id,
where: {
media_: {
title_starts_with: "Cheesy Tune"
}
}
) {
title
media {
title
}
}
} This is the generated SQL query: select 'Song' as entity, to_jsonb(c.*) as data from (select *
from "sgd52"."song" c
where c.block_range @> 1 and ((exists (select 1 from "sgd52"."photo" as i where i."id" = any(c."media") and i.block_range @> 1 and (i."title" like '%Cheesy Tune%')) or exists (select 1 from "sgd52"."video" as i where i."id" = any(c."media") and i.block_range @> 1 and (i."title" like '%Cheesy Tune%'))))
order by "id", block_range
limit 100) c That query is valid and works. The with matches as (select c.* from unnest($1::bytea[]) as q(id)
cross join lateral (select 'Photo' as entity, c.id, c.vid, p.id::text as g$parent_id, c.block_range
/* children_type_c */ from rows from (unnest($2), reduce_dim(array[array['0xf1', '0xf2']]::text[][])) as p(id, child_ids) cross join lateral (select * from "sgd54"."photo" c where c.block_range @> $3 and q.id = p.id and c.id = any(p.child_ids)) c
union all
select 'Video' as entity, c.id, c.vid, p.id::text as g$parent_id, c.block_range
/* children_type_c */ from rows from (unnest($4), reduce_dim(array[array['0xf1', '0xf2']]::text[][])) as p(id, child_ids) cross join lateral (select * from "sgd54"."video" c where c.block_range @> $5 and q.id = p.id and c.id = any(p.child_ids)) c
order by "id", block_range
limit 100) c)
select m.*, to_jsonb("c".*)|| jsonb_build_object('g$parent_id', m.g$parent_id) as data
from "sgd54"."photo" c, matches m
where c.vid = m.vid and m.entity = 'Photo'
union all
select m.*, to_jsonb("c".*)|| jsonb_build_object('g$parent_id', m.g$parent_id) as data
from "sgd54"."video" c, matches m
where c.vid = m.vid and m.entity = 'Video'
order by g$parent_id, "id" Can you confirm it's not supported by graph-node or it's a bug? |
fdee4db
to
37f8143
Compare
graphql/src/schema/api.rs
Outdated
@@ -381,11 +364,14 @@ fn field_list_filter_input_values( | |||
) | |||
} | |||
} | |||
TypeDefinition::Interface(_) => { | |||
TypeDefinition::Interface(parent) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
apologies if I'm missing something (relatively new to rust) but this arm can be combined w line 357 into TypeDefinition::Object(parent) | TypeDefinition::Interface(parent) =>
as in line 231 i think? both of these match expressions (L358-366, L368-375) look the same to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parent
is different in both and I think Rust does not support unions. If I would merge them and try to access parent.name
I would get an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see -- so it works in L231 bc you're discarding the object anyway and here the compiler ~can't understand they both have .name
independently? and so in this case it would need to use .. some kind of trait?
appreciate the response, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch - you can make this work if you destructure further because then name
gets bound to a &String
in both branches:
TypeDefinition::Object(ObjectType { name, .. })
| TypeDefinition::Interface(InterfaceType { name, .. }) => {
if ast::get_derived_from_directive(field).is_some() {
(None, Some(name.clone()))
} else {
(Some(Type::NamedType("String".into())), Some(name.clone()))
}
}
Yes, that is indeed a pre-existing bug related to using I am glad that adding these tests actually uncovered that issue! |
433614e
to
e057635
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! Let's merge it!
})) | ||
}) | ||
.collect::<Result<Vec<EntityFilter>, QueryExecutionError>>()?, | ||
)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow .. nice. That's a pretty nasty case. We'll see how it works out in practice, but it could be that all those or
's end up being slow and we need to change how the query is generated; but it's easier to determine that once we have an example of that.
We did not account for parent ids being `bytea` when generating queries for children type c which only caused trouble when such entities were used in interfaces.
See #3677 (comment) for a list of possible parent/child types and how they are related
e057635
to
93395eb
Compare
Continuation of #3184, adds support for interfaces.
GraphQL Schema
GraphQL Query
SQL statement