Skip to content
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

Partial Objects? (Yes I know its a sin ;-) ) #3

Closed
jensstalder opened this issue Oct 4, 2020 · 12 comments
Closed

Partial Objects? (Yes I know its a sin ;-) ) #3

jensstalder opened this issue Oct 4, 2020 · 12 comments

Comments

@jensstalder
Copy link

jensstalder commented Oct 4, 2020

Hi, just throwing a probably crazy Idea here.

Since graphql defines what fields that need to be hydrated into the result it is a bit superfluous for doctrine to still do selects of entire rows. Now obviously for constancy/sanity doctrine always represents complete entities and therefore selects and hydrates always complete objects, but maybe it's still worth a shot since it would make things much more efficient by only selecting the fields on the SQL level that will have a chance of being represented.

For example in the EntityResolveAbstractFactory.php adding some logic to add the selected fields to the select statement would theoretically work. I just added some from my schema here only for demonstration purposes.

Screenshot 2020-10-04 at 21 57 52

Have partial objects ever crossed your mind?

https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/partial-objects.html

@TomHAnderson
Copy link
Member

Yes! I went down this rabbit hole about a week ago. Though I didn't get as far as calling them a partial object (which is correct.) I spent a couple hours trying to figure out how to get the list of fields into the EntityResolveAbstractFactory. So maybe we can solve this together then move on to using partial objects.

The query builder is fetched here:
https://github.com/API-Skeletons/doctrine-graphql/blob/master/src/Resolve/EntityResolveAbstractFactory.php#L102

Just above that on line 69 I define the function which GraphQL calls. I get
$obj, $args, $context
and somehow we need to get the list of fields for the current query from that data.

I show $obj as null, $args contains the filter, and $context comes from the context set in the GraphQL definition (Use section of docs). So how else can we inject the list of fields into the EntityResolveAbstractFactory?

I investigated the FieldResolver because the ResolveInfo has AST data but that's as far as I made it.

@TomHAnderson
Copy link
Member

TomHAnderson commented Oct 5, 2020

I got to looking at the EntityResolveAbstractFactory and my function definition was incomplete. Adding the ResolveInfo gives a getFieldSelection() function. I've used that to enable partials. Please take a look and give feedback.
#4

@TomHAnderson
Copy link
Member

I don't know of a way to filter relations to a specific list of fields.

@jensstalder
Copy link
Author

Wow nice! Ok, I will check the implementation as soon as I get to it. This obviously would be a huge win for performance.

The relation fields (aka. 2many fields within the entity) themselves don't get partialed. This is by design from doctrine. Or did you mean that nested relations' fields don't get filtered with the current PR? Would this not work anyhow since it's recursive? This would obviously be nice to be consistant, but even filtering out the root entity is already something.

Anyhow I will check.

@TomHAnderson
Copy link
Member

I dug into the code and was able to get lazy loading collections working. That was an ugly secret of this library but they are proper now. I had to remove the memberOf filter to make the collections work so I'm looking at creating a new major version of this library.

@jensstalder
Copy link
Author

Finally got around to it. This works great!

Subsequent selects for relationships don't seam to work though. But even reducing the main entity is nice ;-).

Here is my simple Example:

Screenshot 2020-10-06 at 22 59 11
Screenshot 2020-10-06 at 23 00 00

The fist initial query is now filtered. But the subsequent queries include fields that are not in the query.

Another thing to look out for are for any relationships using: fetch="EAGER". Looks like then it left joins the toOne relationship without considering the partial definition. So either make sure fetch="LAZY" is defined or not at all.

When the endpoint is being used primarily using this graphQL endpoint, it largely wont make sense to make toOne relationships eager since they are not returned (except if mentioned in the query) anyhow.

@jensstalder
Copy link
Author

Oh! didn't reallize it. But in my example it's selecting PropertyTags even though its not mentioned in the query. hmmm weird.

@TomHAnderson
Copy link
Member

Do you have the latest branch data? I only just added lazy association handling. So check your commit history and this should be your latest commit: d0b5266

@jensstalder
Copy link
Author

Yup i'm on the latest commit from /feature/partial. There might be something wonky with my doctrine schema. I'm using an actual productive schema. That way edge cases can prop up (which is not a bad thing). I'll try again later. Thanks again.

@TomHAnderson
Copy link
Member

Looking forward to your review and comments, @jensstalder

@jensstalder
Copy link
Author

Sorry forgot. :-)

I tested it again and it appears to be still the case that the root entity gets "partialed" but not the relationships. It's already an improvement, so to me this looks great :-). The only thing I tried was adding all the relationships as LeftJoins within EntityResolveAbstractFactory to see if they can be "partialed" there, but obviously that is not a good solution since left joining is very dependent. I did not figure this out however. It did consolidate everything down to one query, but this always seams like a benefit on the surface, but in the end multiple selects can actually perform much fasters in certain situations especially when a lot of sorting and counting/limiting is in use which is why I don't mind the multiple selects.

Since the queryBuilder is used the "EAGER, LAZY" annotations are probably not respected anyhow right?

I am not familiar with the criteria implementation, but as far as I understand this creates the filters for relationships beyond the root entity?

I also have a case where basic filters are not working on the root entity but work on relationships. But maybe my schema is not being parsed 100% correctly? There are some traits within the entity classes where the crawler might have issues?

@TomHAnderson
Copy link
Member

EAGER, LAZY, EXTRA LAZY is respected.

Criteria is for non-root only

If you have a case which doesn't work please create a unit test for it so it can be fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants