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

Schema renaming: Interface implementation suppression #1002

Open
wants to merge 22 commits into
base: main
Choose a base branch
from

Conversation

LWprogramming
Copy link
Collaborator

@LWprogramming LWprogramming commented Mar 17, 2021

Interfaces present a challenge for schema renaming that don't show up with regular object types uninvolved with interfaces.

This PR implements suppressing object types that implement interfaces. The main issue is that suppress an object type Foo that implements an interface Bar can cause problems if, later on, someone queries for all objects that implement Bar and ends up getting objects of suppressed type Foo. If this happens, we remove the interface (as well as all its ancestor interfaces*) from RootSchemaQuery to make it unqueryable.

*For the directed graph consisting of all types in the schema as nodes and the edges are from an interface to a type that implements it (for every such interface and type pair), an interface is an ancestor of another type iff there's a path from the interface's node to that type's node.

@codecov
Copy link

codecov bot commented Mar 18, 2021

Codecov Report

Merging #1002 (ee3048c) into main (44c63aa) will increase coverage by 0.02%.
The diff coverage is 100.00%.

❗ Current head ee3048c differs from pull request most recent head 2d997ee. Consider uploading reports for the commit 2d997ee to get more accurate results
Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1002      +/-   ##
==========================================
+ Coverage   94.38%   94.40%   +0.02%     
==========================================
  Files         113      113              
  Lines        9080     9133      +53     
==========================================
+ Hits         8570     8622      +52     
- Misses        510      511       +1     
Impacted Files Coverage Δ
...ql_compiler/schema_transformation/rename_schema.py 99.66% <100.00%> (-0.34%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 44c63aa...2d997ee. Read the comment docs.

renamed_schema_ast = visit(schema_ast, visitor)
return renamed_schema_ast


def _recursively_get_ancestor_interface_names(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Necessary to define this function outside the RenameSchemaTypesVisitor object because we need access to the schema's definitions field in order to get the interface definition nodes.

@LWprogramming LWprogramming changed the title Schema renaming: Interface and implementation suppression Schema renaming: Interface implementation suppression Mar 19, 2021
@LWprogramming LWprogramming marked this pull request as ready for review March 19, 2021 15:42
LWprogramming added a commit that referenced this pull request Mar 23, 2021
This depends on some other code being merged into main, such as the code
for interface implementation suppression in PR #1002, so this is just a
rough draft at the moment and just contains the rough outline for what
this code would look like.
graphql_compiler/schema_transformation/rename_schema.py Outdated Show resolved Hide resolved
Comment on lines +94 to +100
- If you suppress a type Foo implementing an interface Bar, then Bar will remain in the schema but
not in the root query type, making it unqueryable. This is to prevent situations where a scope in
a query is of type Bar without also being of some more specific type, which may yield vertices of
type Foo even though it was suppressed. For this reason, all interfaces that Bar implements will
also be made unqueryable in the same way, and this removal from the root query type will happen
recursively until all interfaces that Foo implements (either directly or indirectly) are
unqueryable.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this comment could be updated to mention that field interface suppression is not yet implemented i.e. what this test is addressing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, so this is a bit tricky-- the plan for field renamings in PR 1005 is more like allowing something that should already exist, specifically if we have a schema like

# Exists interface named Foo with a single field named id
type Bar implements Foo {
  name: String
  id: String
}

then it should be legal to rename the name field, just like if Bar implemented no interfaces. In other words, it seems like it's less of a large functionality change that's coming soon and more like allowing something that should be allowed without adding more special cases to the field renaming API, had the NotImplementedError checks not been so strict.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the case in the test case I linked above going to be implemented eventually or will it always be a CascadingSuppressionError? If it is going to be implemented eventually, maybe in the Operations that are not yet supported but will be implemented: section there should be something like automatic suppression of fields when the field is of a suppressed interface type?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation will always be a CascadingSuppressionError-- the problem in that test case is that the Giraffe type's friend field is of type Character, and Character is to be made unqueryable. However, there's no way to suppress any fields in Giraffe right now because Giraffe implements an interface, so until the changes in #1005 relax this restriction, there's no way to implement the part of the test that shows how we'd want to do such a thing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I'm not proposing you implement in this PR, but I think it should be documented somewhere that you can't suppress an object that implements an interface if that interfaces is a field type on another object. Is that already documented somewhere that I missed?

graphql_compiler/schema_transformation/rename_schema.py Outdated Show resolved Hide resolved
graphql_compiler/schema_transformation/rename_schema.py Outdated Show resolved Hide resolved
graphql_compiler/schema_transformation/rename_schema.py Outdated Show resolved Hide resolved
graphql_compiler/schema_transformation/rename_schema.py Outdated Show resolved Hide resolved
LWprogramming and others added 7 commits April 5, 2021 12:11
Co-authored-by: chewselene <52711428+chewselene@users.noreply.github.com>
Co-authored-by: chewselene <52711428+chewselene@users.noreply.github.com>
Co-authored-by: chewselene <52711428+chewselene@users.noreply.github.com>
Comment on lines 259 to 262
type_not_suppressed = type_renamings[type_name] is not None
if type_not_suppressed:
continue
if type_name in interface_and_object_type_name_to_definition_node_map:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type_not_suppressed = type_renamings[type_name] is not None
if type_not_suppressed:
continue
if type_name in interface_and_object_type_name_to_definition_node_map:
# <comment>
if type_renamings[type_name] is None and type_name in interface_and_object_type_name_to_definition_node_map:

nit: this seems like it could all be done in 1 conditional. What does it mean if type_name in interface_and_object_type_name_to_definition_node_map is False?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit unfortunate-- so, as it is, we need to compute which interfaces should be made unqueryable before doing any sort of renaming. However, all the error handling for unused type renamings happens after all the renaming is done. If type_name, an entry in type_renamings, is not in the map, then type_name doesn't actually correspond to a type in the schema, and it should have no effect. Now that I look at this again, there might be a better way to organize this but I'm not quite sure how might work best-- open to suggestions!

(I suppose this also affects the first part, where we might do this in a single conditional.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When type_name not in interface_and_object_type_name_to_definition_node_map, the user is attempting to rename something that is not in the schema, right? It seems like that should be an error to me because renaming something that doesn't exist does not make sense. Am I understanding the meaning correctly, and, if so, any reasons this shouldn't be an error?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does end up as an error, but the error handling happens later on. The structure of the code, before this PR, is along the lines of:

  1. Go through the schema, applying renamings via the visitor pattern
  2. If there are any unused renamings, then raise an error saying which renamings were unused.

Now, we're in a bit of a catch-22 situation: the point of the visitor is that we don't have access to the entire schema-- only individual nodes at any one time. However, we can't find out which interface nodes to be made unqueryable without schema_ast.definitions, which needs to be done outside a visitor object (and therefore before any detection of unused renamings).

The workaround that this PR currently uses is basically ignoring unused type renamings because they'll be handled later on in the helper functions that use visitor objects. Hacky, but I'm not sure if we have other options here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming you want to hold off on raising an error message until later because it will be a more complete error message with all the issues in the schema renaming, right? This could definitely benefit from a comment explaining that and I still think it can just be 1 conditional.

Comment on lines +94 to +100
- If you suppress a type Foo implementing an interface Bar, then Bar will remain in the schema but
not in the root query type, making it unqueryable. This is to prevent situations where a scope in
a query is of type Bar without also being of some more specific type, which may yield vertices of
type Foo even though it was suppressed. For this reason, all interfaces that Bar implements will
also be made unqueryable in the same way, and this removal from the root query type will happen
recursively until all interfaces that Foo implements (either directly or indirectly) are
unqueryable.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the case in the test case I linked above going to be implemented eventually or will it always be a CascadingSuppressionError? If it is going to be implemented eventually, maybe in the Operations that are not yet supported but will be implemented: section there should be something like automatic suppression of fields when the field is of a suppressed interface type?

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

Successfully merging this pull request may close these issues.

None yet

2 participants