-
Notifications
You must be signed in to change notification settings - Fork 272
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
test fragment in requires directive #3978
Conversation
@Geal, please consider creating a changeset entry in |
CI performance tests
|
if __typename is not present or null, we can still infer it using the schema, and repair the selections
@@ -48,97 +48,171 @@ pub(crate) struct InlineFragment { | |||
pub(crate) selections: Vec<Selection>, | |||
} | |||
|
|||
pub(crate) fn select_object( | |||
content: &Object, | |||
pub(crate) fn execute_selection_set<'a>( |
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.
this replaces our initial implementation with a function that closely follows what is happening in the gateway's executeSelectionSet
function
// if the __typename field was missing but we can infer it, fill it | ||
if let Some(ty) = current_type { | ||
output.insert( | ||
ByteString::from(selection_name.to_owned()), | ||
Value::String(ByteString::from(ty.to_owned())), | ||
); | ||
continue; | ||
} | ||
} | ||
// the behaviour here does not align with the gateway: we should instead assume that | ||
// data is in the correct shape, and return a null (or even no value at all) on | ||
// missing fields. If a field was missing, it should have been nullified, | ||
// and if it was non nullable, the parent object would have been nullified. | ||
// Unfortunately, we don't validate subgraph responses yet | ||
if field_type | ||
.as_ref() | ||
.map(|ty| !ty.is_non_null()) | ||
.unwrap_or(false) | ||
{ | ||
output.insert(ByteString::from(selection_name.to_owned()), Value::Null); | ||
} else { | ||
return Value::Null; | ||
} |
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 am not entirely confident about this part because it differs from the gateway's implementation. The rationale from the gateway's side is that a missing field in the data we select from should not have happened, and should have been caught by response validation, but I don't see where this validation is happening
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.
@trevor-scheer @clenfest is something you could help us figure out? 🙏
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.
From memory (a long time ago) I was under the impression that we never validated responses from subgraphs as this was an expensive operation.
if name.as_str() == TYPENAME { | ||
let input_value = input | ||
.get(field_name.as_str()) | ||
.cloned() | ||
.filter(|v| v.is_string()) | ||
.unwrap_or_else(|| { | ||
Value::String(ByteString::from( | ||
parent_type.inner_named_type().as_str().to_owned(), | ||
)) | ||
}); |
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.
this is specifically a check to replace a null __typename
with the type we currently know. Maybe in general, we should make sure to replace the typename with the one we know from the query and the API schema
} | ||
|
||
#[tokio::test] | ||
async fn typename_propagation2() { |
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.
this test is still failing. I do not know yet if we should fix it (it requires some work to find out the right type) or accept that the implementation does what it can, and won't cover all cases (missing __typename
can generally be fixed by adding it to the @requires
selection)
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.
What shall we do with this? Raise a followup ticket?
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.
Looking good overall, i just got a bit nitpicky trying to understand things.
The failing test i d love us to investigate a bit (looks like a breaking change?)
@@ -131,6 +131,7 @@ impl Variables { | |||
request: &Arc<http::Request<Request>>, | |||
schema: &Schema, | |||
input_rewrites: &Option<Vec<rewrites::DataRewrite>>, | |||
//document: &ParsedDocument, |
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.
//document: &ParsedDocument, |
let mut output = Object::with_capacity(selections.len()); | ||
for selection in selections { | ||
println!( |
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.
println!( | |
tracing::trace!( |
// if the __typename field was missing but we can infer it, fill it | ||
if let Some(ty) = current_type { | ||
output.insert( | ||
ByteString::from(selection_name.to_owned()), | ||
Value::String(ByteString::from(ty.to_owned())), | ||
); | ||
continue; | ||
} | ||
} | ||
// the behaviour here does not align with the gateway: we should instead assume that | ||
// data is in the correct shape, and return a null (or even no value at all) on | ||
// missing fields. If a field was missing, it should have been nullified, | ||
// and if it was non nullable, the parent object would have been nullified. | ||
// Unfortunately, we don't validate subgraph responses yet | ||
if field_type | ||
.as_ref() | ||
.map(|ty| !ty.is_non_null()) | ||
.unwrap_or(false) | ||
{ | ||
output.insert(ByteString::from(selection_name.to_owned()), Value::Null); | ||
} else { | ||
return Value::Null; | ||
} |
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.
@trevor-scheer @clenfest is something you could help us figure out? 🙏
println!( | ||
"is_object_of_type({condition})={b} for {}", | ||
serde_json::to_string(&content).unwrap(), | ||
); |
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.
println!( | |
"is_object_of_type({condition})={b} for {}", | |
serde_json::to_string(&content).unwrap(), | |
); | |
tracing::trace!( | |
"is_object_of_type({condition})={b} for {}", | |
serde_json::to_string(&content).unwrap(), | |
); |
println!( | ||
"execute_selection_set: output is now: {}", | ||
serde_json::to_string(&output).unwrap(), | ||
); |
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.
println!( | |
"execute_selection_set: output is now: {}", | |
serde_json::to_string(&output).unwrap(), | |
); | |
tracing::trace!( | |
"execute_selection_set: output is now: {}", | |
serde_json::to_string(&output).unwrap(), | |
); |
if conditional_type.is_interface() || conditional_type.is_union() { | ||
return (object_type.is_object() || object_type.is_interface()) | ||
&& schema.is_subtype(condition, typename); | ||
} | ||
|
||
false |
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.
if conditional_type.is_interface() || conditional_type.is_union() { | |
return (object_type.is_object() || object_type.is_interface()) | |
&& schema.is_subtype(condition, typename); | |
} | |
false | |
(conditional_type.is_interface() || conditional_type.is_union()).then(|| (object_type.is_object() || object_type.is_interface()) | |
&& schema.is_subtype(condition, typename)).unwrap_or(false) |
not tested, i just wanted to be nitpicky 😂
/*match (value, selections) { | ||
(Value::Object(content), requires) => { | ||
select_object(content, requires, schema).transpose() | ||
select_object(content, requires, schema) //.transpose() | ||
} | ||
(_, _) => Some(Err(FetchError::ExecutionInvalidContent { | ||
(_, _) => Err(FetchError::ExecutionInvalidContent { | ||
reason: "not an object".to_string(), | ||
})), | ||
}),*/ |
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.
uh did we remove a test? :D
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.
no, the select_object
was just replaced with execute_selection_set
)) | ||
.collect::<Vec<_>>(), | ||
)); | ||
println!("select res: {res:?}"); |
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.
println!("select res: {res:?}"); |
.await | ||
.unwrap(); | ||
|
||
println!("\n\n==================QUERYBOOK2=============\n\n"); |
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.
println!("\n\n==================QUERYBOOK2=============\n\n"); |
} | ||
|
||
#[tokio::test] | ||
async fn typename_propagation2() { |
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.
What shall we do with this? Raise a followup ticket?
This is a case that this PR can't fix. We will need to revisit this when we get deeper integration with the compiler and the new planner |
related to #3972
Fix #3983 (not yet, this only contains tests for now)
Fix #4052
The representation selection code in
router/apollo-router/src/query_planner/selection.rs
Line 105 in 3eb70b7
should be able to recognize the current type even when
__typename
is not present if it's a concrete type. This could be achieved by passing the query compiler along the selection code, or using theSchema
representation.The plan right now:
@requires
: https://www.apollographql.com/docs/federation/entities-advanced/#contributing-computed-entity-fieldsParsedDocument
toVariables::new
(getting it from context)ExecutableDocument
and aPath
and returns aField
with its type and selection setselect_object
and friends so we know exactly which type is required wherecould we work with the schema instead of the query document? (since the schema is already transmitted there).
we may need to work from the query because the path depends on the query(example: there was an alias somewhere).
we would need to work with the schema because the
@requires
selection are not present in the query.can we fix the requires selection by post-processing the query plan to add type information then caching the result?
Checklist
Complete the checklist (and note appropriate exceptions) before the PR is marked ready-for-review.
Exceptions
Note any exceptions here
Notes
Footnotes
It may be appropriate to bring upcoming changes to the attention of other (impacted) groups. Please endeavour to do this before seeking PR approval. The mechanism for doing this will vary considerably, so use your judgement as to how and when to do this. ↩
Configuration is an important part of many changes. Where applicable please try to document configuration examples. ↩
Tick whichever testing boxes are applicable. If you are adding Manual Tests, please document the manual testing (extensively) in the Exceptions. ↩