-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat(graph,graphql): filter by child entity (interfaces) #3677
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
Conversation
3be9131 to
15ffb98
Compare
lutter
left a comment
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
| 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) cThat 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
| } | ||
| } | ||
| 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
lutter
left a comment
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