Skip to content

Conversation

@bbakerman
Copy link
Member

@bbakerman bbakerman commented Nov 28, 2024

Previously the ENO compiler would not respect "immediate" directives on a field.

While it has the QueryDirectives object - this doe not have "Normalised" values for directive arguments.

This PR adds that. This PR is more extensive that I had hoped - but adding Normalised values in multiple places will set us up for betterness later

@bbakerman bbakerman added this to the 23.x breaking changes milestone Nov 28, 2024
when:
def result = localCompileToDocument(schema, QUERY, null, fields, noVariables)
def printed = AstPrinter.printAst(new AstSorter().sort(result.document))
def printed = printDoc(result.document)
Copy link
Member Author

Choose a reason for hiding this comment

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

just to clean up the tests a little



def "test a combination of plain objects and interfaces will be all variables"() {
def "test a combination of plain objects and interfaces with all variables and no variables"() {
Copy link
Member Author

Choose a reason for hiding this comment

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

I added two test states here - with all variables and with no variables

v3: [arg1: "barFragArg"],
v4: [arg1: "barArg"],
v5: [arg1: "fooArg"]]

Copy link
Member Author

Choose a reason for hiding this comment

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

This is the extra test state. Using no variables

listField1(arg: [1, 2, 3])
listField2(arg: [{arg1 : "v1", arg2 : [{arg1 : "v1.1"}]}, {arg1 : "v2"}, {arg1 : "v3"}])
}
'''
Copy link
Member Author

Choose a reason for hiding this comment

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

This works - all the values are inlined as expected

}

private ExecutableNormalizedOperation createNormalizedTree(GraphQLSchema schema, String query, Map<String, Object> variables = [:]) {
def "can extract variables or inline values for directives on the query"() {
Copy link
Member Author

Choose a reason for hiding this comment

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

Added this test for directives - it does NOT work - directives are ignored and removed

@github-actions
Copy link
Contributor

github-actions bot commented Nov 28, 2024

Test Results

  311 files    311 suites   47s ⏱️
3 568 tests 3 562 ✅ 6 💤 0 ❌
3 657 runs  3 651 ✅ 6 💤 0 ❌

Results for commit 053aac4.

♻️ This comment has been updated with latest results.

bar(barArg: "barArgVar") @optIn(to: ["optToX"])
}
}
'''
Copy link
Member Author

Choose a reason for hiding this comment

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

This currently produces

query named {
  foo(fooArg: "fooArgVar") {
    bar(barArg: "barArgVar")
  }
}

Which is wrong!!

bar(barArg: $v0)
}
}
'''
Copy link
Member Author

Choose a reason for hiding this comment

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

The above is also wrong but I made the assertion pass for now - so I can run both tests. But I know this to be wrong

Directive directive = directives.get(i);
GraphQLDirective graphQLDirective = resolvedDirectives.get(i);
directiveCounterParts.put(graphQLDirective, directive);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Question - this is mapping the AST directive to its GraphQLDirective counter part by index - does directivesResolver return the list in the same order and size.

@bbakerman bbakerman changed the title Extra tests for document compiler - handles directives and values down in the tree Allowing document compiler to handles directives and values down in the field tree Dec 5, 2024
* argument value making.
*/
@Internal
class ArgumentMaker {
Copy link
Member Author

Choose a reason for hiding this comment

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

as the comment says - breaking out code into its own class

Copy link
Member

Choose a reason for hiding this comment

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

I like the class name, reminds me of this Monty Python sketch https://www.youtube.com/watch?v=uLlv_aZjHXc

I'd like to have an argument please

String argName,
NormalizedInputValue normalizedInputValue) {
return false;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

since this is a SPI - we cant require them to implement this. Or we could make it a breaking change say

normalizedVariableValues = ValuesResolver.getNormalizedVariableValues(graphQLSchema,
variableDefinitions,
inputVariables,
executionInput.getGraphQLContext(), executionInput.getLocale());
Copy link
Member Author

Choose a reason for hiding this comment

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

This is new - we will coerce variables into rutime form AND Normalised literal form so that the ExecutionContext can have them AND the QueryDirectives can have them in the DFE

public NormalizedVariables getNormalizedVariables() {
return normalizedVariables;
}

Copy link
Member Author

Choose a reason for hiding this comment

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

just passing on NormalizedVariables

QueryDirectives queryDirectives = new QueryDirectivesImpl(field,
executionContext.getGraphQLSchema(),
executionContext.getCoercedVariables().toMap(),
executionContext.getNormalizedVariables().toMap(),
Copy link
Member Author

Choose a reason for hiding this comment

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

needed inside to make normalized values where they refeference operation variables

public String toString() {
return normalisedVariables.toString();
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

We have Raw, Coerced and NormalizedVariables now as distinct classes


return result;

return NormalizedVariables.of(result);
Copy link
Member Author

Choose a reason for hiding this comment

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

distinct class now used

}

public Map<String, List<GraphQLDirective>> resolveDirectives(List<Directive> directives, GraphQLSchema schema, Map<String, Object> variables, GraphQLContext graphQLContext, Locale locale) {
public BiMap<GraphQLDirective, Directive> resolveDirectives(List<Directive> directives, GraphQLSchema schema, Map<String, Object> variables, GraphQLContext graphQLContext, Locale locale) {
Copy link
Member Author

Choose a reason for hiding this comment

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

The only use of this internal class is QueryDirectivesImpl - so we can change this with impunity

Copy link
Member Author

Choose a reason for hiding this comment

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

Map<String, List> was a pointless modelling - BiMap<GraphQLDirective, Directive is what we need

*
* @return a map of applied directive to named argument values
*/
Map<QueryAppliedDirective, Map<String, NormalizedInputValue>> getNormalizedInputValueByImmediateAppliedDirectives();
Copy link
Member Author

Choose a reason for hiding this comment

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

for a given QueryAppliedDirective - we can get a normalised view of its argument values

private static SelectionSet selectionSet(List<Field> fields) {
return newSelectionSet().selections(fields).build();
}

Copy link
Member Author

Choose a reason for hiding this comment

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

The code below got moved into ArgumentMaker

.graphQLContext({ it.put(InputInterceptor.class, interceptor) })
.variables(
"booleanArg": "truthy",
"booleanArg": false,
Copy link
Member Author

Choose a reason for hiding this comment

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

This was never a valid boolean value

@Override
StringValue valueToLiteral(@NotNull Object input, @NotNull GraphQLContext graphQLContext, @NotNull Locale locale) {
return new StringValue(input.toString())
}
Copy link
Member Author

Choose a reason for hiding this comment

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

This is now needed because during "execution" we create normalised values.

This is something contentious. If a SCALAR did not previously have this, they require it now to work.

So we want this. We need to discuss but I think so otherwise I would not have put it in place

Copy link
Member

Choose a reason for hiding this comment

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

That's very interesting! I want to chat about this too

import static graphql.normalized.ExecutableNormalizedOperationToAstCompiler.compileToDocumentWithDeferSupport

abstract class ExecutableNormalizedOperationToAstCompilerTest extends Specification {
abstract class ENOToAstCompilerTestBase extends Specification {
Copy link
Member Author

Choose a reason for hiding this comment

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

made a common base - this class is getting WAY to big

static printDoc(Document doc) {
return AstPrinter.printAst(sortDoc(doc))
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

most of the above is moving common test helper code around

name @optIn(to: "devOps")
foo1(arg: {arg1 : "Subscription"}) @optIn(to: ["foo"]) {
name @optIn(to: ["devOps"])
}
Copy link
Member Author

Choose a reason for hiding this comment

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

was NOT correct before

executionInput.getGraphQLContext(),
executionInput.getLocale());

normalizedVariableValues = FpKit.intraThreadMemoize(() ->
Copy link
Member Author

Choose a reason for hiding this comment

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

I made this a supplier - if not one asks for normalised values of directives - then no work will be done.

QueryDirectivesIpl is also lazy by design -so this wont be asked for until some one asks for QueryDirectives

Copy link
Member

Choose a reason for hiding this comment

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

Nice

return variablePredicate != null && variablePredicate.shouldMakeVariable(executableNormalizedField, queryAppliedDirective, argName, normalizedInputValue);
} else {
return variablePredicate != null && variablePredicate.shouldMakeVariable(executableNormalizedField, argName, normalizedInputValue);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

this internal call back is the same - when queryAppliedDirective is null - it must be just for a field

Copy link
Member

Choose a reason for hiding this comment

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

This could be added as a comment in the code, that the else branch is only for a field

Copy link
Member Author

Choose a reason for hiding this comment

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

done

}
}
}
'''
Copy link
Member Author

Choose a reason for hiding this comment

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

notice all the fragment directives

fragment InnerDetails on Book @timeout(afterMillis: 27)

well they dont get picked up as "immediate" query directives. This is NOT new behavior

Only immediate field directives are captured

* Test related to ENO and directives
*/
class ENOToAstCompilerDirectivesTest extends ENOToAstCompilerTestBase {
class ExecutableNormalizedOperationToAstCompilerDirectivesTest extends ENOToAstCompilerTestBase {
Copy link
Member Author

Choose a reason for hiding this comment

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

name line up better in IDEA - even though its TOO long a class name

vars == [v0: "barArgVar", v1: ["optToX"], v2: ["optToY"], v3: "fooArgVar", v4: false]
//
// the below is what ir currently produces but its WRONG
// fix up when the other tests starts to work
Copy link
Member

Choose a reason for hiding this comment

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

Is this test working as expected now?

…ument-compiler

# Conflicts:
#	src/main/java/graphql/execution/Execution.java
#	src/main/java/graphql/normalized/ExecutableNormalizedOperationToAstCompiler.java
@bbakerman bbakerman merged commit 472bb92 into master Mar 20, 2025
2 checks passed
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.

3 participants