Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import graphql.language.TypeName;
import graphql.language.Value;
import graphql.schema.GraphQLSchema;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.LinkedHashMap;
Expand All @@ -32,32 +34,65 @@

@Internal
public class ExecutableNormalizedOperationToAstCompiler {
public static Document compileToDocument(GraphQLSchema schema,
OperationDefinition.Operation operationKind,
String operationName,
List<ExecutableNormalizedField> topLevelFields) {
List<Selection<?>> selections = selectionsForNormalizedFields(schema, topLevelFields);

public static class CompilerResult {
private final Document document;
private final Map<String, Object> variables;

public CompilerResult(Document document, Map<String, Object> variables) {
this.document = document;
this.variables = variables;
}

public Document getDocument() {
return document;
}

public Map<String, Object> getVariables() {
return variables;
}
}

public static CompilerResult compileToDocument(
GraphQLSchema schema,
OperationDefinition.Operation operationKind,
String operationName,
List<ExecutableNormalizedField> topLevelFields,
VariablePredicate variablePredicate
) {
VariableAccumulator variableAccumulator = new VariableAccumulator(variablePredicate);
List<Selection<?>> selections = selectionsForNormalizedFields(schema, topLevelFields, variableAccumulator);
SelectionSet selectionSet = new SelectionSet(selections);

return Document.newDocument()
.definition(OperationDefinition.newOperationDefinition()
.name(operationName)
.operation(operationKind)
.selectionSet(selectionSet)
.build())
.build();

OperationDefinition.Builder definitionBuilder = OperationDefinition.newOperationDefinition()
.name(operationName)
.operation(operationKind)
.selectionSet(selectionSet);

definitionBuilder.variableDefinitions(variableAccumulator.getVariableDefinitions());

return new CompilerResult(
Document.newDocument()
.definition(definitionBuilder.build())
.build(),
variableAccumulator.getVariablesMap()
);
}

private static List<Selection<?>> selectionsForNormalizedFields(GraphQLSchema schema,
List<ExecutableNormalizedField> executableNormalizedFields) {
private static List<Selection<?>> selectionsForNormalizedFields(
GraphQLSchema schema,
List<ExecutableNormalizedField> executableNormalizedFields,
VariableAccumulator variableAccumulator
) {
ImmutableList.Builder<Selection<?>> selections = ImmutableList.builder();

// All conditional fields go here instead of directly to selections so they can be grouped together
// in the same inline fragement in the output
Map<String, List<Field>> conditionalFieldsByObjectTypeName = new LinkedHashMap<>();

for (ExecutableNormalizedField nf : executableNormalizedFields) {
Map<String, List<Field>> groupFieldsForChild = selectionForNormalizedField(schema, nf);
Map<String, List<Field>> groupFieldsForChild = selectionForNormalizedField(schema, nf, variableAccumulator);
if (nf.isConditional(schema)) {
groupFieldsForChild.forEach((objectTypeName, fields) -> {
List<Field> fieldList = conditionalFieldsByObjectTypeName.computeIfAbsent(objectTypeName, ignored -> new ArrayList<>());
Expand All @@ -81,18 +116,21 @@ private static List<Selection<?>> selectionsForNormalizedFields(GraphQLSchema sc
return selections.build();
}

private static Map<String, List<Field>> selectionForNormalizedField(GraphQLSchema schema,
ExecutableNormalizedField executableNormalizedField) {
private static Map<String, List<Field>> selectionForNormalizedField(
GraphQLSchema schema,
ExecutableNormalizedField executableNormalizedField,
VariableAccumulator variableAccumulator
) {
Map<String, List<Field>> groupedFields = new LinkedHashMap<>();
for (String objectTypeName : executableNormalizedField.getObjectTypeNames()) {
List<Selection<?>> subSelections = selectionsForNormalizedFields(schema, executableNormalizedField.getChildren());
List<Selection<?>> subSelections = selectionsForNormalizedFields(schema, executableNormalizedField.getChildren(), variableAccumulator);
SelectionSet selectionSet = null;
if (subSelections.size() > 0) {
selectionSet = newSelectionSet()
.selections(subSelections)
.build();
}
List<Argument> arguments = createArguments(executableNormalizedField);
List<Argument> arguments = createArguments(executableNormalizedField, variableAccumulator);
Field field = newField()
.name(executableNormalizedField.getFieldName())
.alias(executableNormalizedField.getAlias())
Expand All @@ -109,30 +147,33 @@ private static SelectionSet selectionSet(List<Field> fields) {
return newSelectionSet().selections(fields).build();
}

private static List<Argument> createArguments(ExecutableNormalizedField executableNormalizedField) {
private static List<Argument> createArguments(ExecutableNormalizedField executableNormalizedField, VariableAccumulator variableAccumulator) {
ImmutableList.Builder<Argument> result = ImmutableList.builder();
ImmutableMap<String, NormalizedInputValue> normalizedArguments = executableNormalizedField.getNormalizedArguments();
for (String argName : normalizedArguments.keySet()) {
NormalizedInputValue normalizedInputValue = normalizedArguments.get(argName);
Value<?> value = argValue(executableNormalizedField, argName, normalizedInputValue, variableAccumulator);
Argument argument = newArgument()
.name(argName)
.value(argValue(normalizedArguments.get(argName).getValue()))
.value(value)
.build();
result.add(argument);
}
return result.build();
}

private static Value<?> argValue(Object value) {
@SuppressWarnings("unchecked")
private static Value<?> argValue(ExecutableNormalizedField executableNormalizedField, String argName, @Nullable Object value, VariableAccumulator variableAccumulator) {
if (value instanceof List) {
ArrayValue.Builder arrayValue = ArrayValue.newArrayValue();
arrayValue.values(map((List<Object>) value, ExecutableNormalizedOperationToAstCompiler::argValue));
arrayValue.values(map((List<Object>) value, val -> argValue(executableNormalizedField, argName, val, variableAccumulator)));
return arrayValue.build();
}
if (value instanceof Map) {
ObjectValue.Builder objectValue = ObjectValue.newObjectValue();
Map<String, Object> map = (Map<String, Object>) value;
for (String fieldName : map.keySet()) {
Value<?> fieldValue = argValue(((NormalizedInputValue) map.get(fieldName)).getValue());
Value<?> fieldValue = argValue(executableNormalizedField, argName, (NormalizedInputValue) map.get(fieldName), variableAccumulator);
objectValue.objectField(ObjectField.newObjectField().name(fieldName).value(fieldValue).build());
}
return objectValue.build();
Expand All @@ -142,4 +183,14 @@ private static Value<?> argValue(Object value) {
}
return (Value<?>) value;
}

@NotNull
private static Value<?> argValue(ExecutableNormalizedField executableNormalizedField, String argName, NormalizedInputValue normalizedInputValue, VariableAccumulator variableAccumulator) {
if (variableAccumulator.shouldMakeVariable(executableNormalizedField, argName, normalizedInputValue)) {
VariableValueWithDefinition variableWithDefinition = variableAccumulator.accumulateVariable(normalizedInputValue);
return variableWithDefinition.getVariableReference();
} else {
return argValue(executableNormalizedField, argName, normalizedInputValue.getValue(), variableAccumulator);
}
}
}
128 changes: 128 additions & 0 deletions src/main/java/graphql/normalized/ValueToVariableValueCompiler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package graphql.normalized;

import graphql.AssertException;
import graphql.Internal;
import graphql.language.ArrayValue;
import graphql.language.BooleanValue;
import graphql.language.FloatValue;
import graphql.language.IntValue;
import graphql.language.NullValue;
import graphql.language.ObjectField;
import graphql.language.ObjectValue;
import graphql.language.StringValue;
import graphql.language.TypeName;
import graphql.language.Value;
import graphql.language.VariableDefinition;
import graphql.language.VariableReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static graphql.collect.ImmutableKit.map;
import static java.util.stream.Collectors.toList;

@Internal
public class ValueToVariableValueCompiler {

static VariableValueWithDefinition normalizedInputValueToVariable(NormalizedInputValue normalizedInputValue, int queryVariableCount) {
Object variableValue = normalisedValueToVariableValue(normalizedInputValue);
String varName = getVarName(queryVariableCount);
return new VariableValueWithDefinition(
variableValue,
VariableDefinition.newVariableDefinition()
.name(varName)
.type(TypeName.newTypeName(normalizedInputValue.getTypeName()).build())
.build(),
VariableReference.newVariableReference().name(varName).build());
}

@SuppressWarnings("unchecked")
@Nullable
private static Object normalisedValueToVariableValue(Object maybeValue) {
Object variableValue;
if (maybeValue instanceof NormalizedInputValue) {
NormalizedInputValue normalizedInputValue = (NormalizedInputValue) maybeValue;
Object inputValue = normalizedInputValue.getValue();
if (inputValue instanceof Value) {
variableValue = toVariableValue((Value<?>) inputValue);
} else if (inputValue instanceof List) {
variableValue = normalisedValueToVariableValues((List<Object>) inputValue);
} else if (inputValue instanceof Map) {
variableValue = normalisedValueToVariableValues((Map<String, Object>) inputValue);
} else {
throw new AssertException("Should never happen. Did not expect NormalizedInputValue.getValue() of type: " + inputValue.getClass());
}
} else if (maybeValue instanceof Value) {
Value<?> value = (Value<?>) maybeValue;
variableValue = toVariableValue(value);
} else if (maybeValue instanceof List) {
variableValue = normalisedValueToVariableValues((List<Object>) maybeValue);
} else if (maybeValue instanceof Map) {
variableValue = normalisedValueToVariableValues((Map<String, Object>) maybeValue);
} else {
throw new AssertException("Should never happen. Did not expect type: " + maybeValue.getClass());
}
return variableValue;
}

private static List<Object> normalisedValueToVariableValues(List<Object> arrayValues) {
return map(arrayValues, ValueToVariableValueCompiler::normalisedValueToVariableValue);
}

@NotNull
private static Map<String, Object> normalisedValueToVariableValues(Map<String, Object> objectMap) {
Map<String, Object> output = new LinkedHashMap<>();
objectMap.forEach((k, v) -> {
Object value = normalisedValueToVariableValue(v);
output.put(k, value);
});
return output;
}

private static Map<String, Object> toVariableValue(ObjectValue objectValue) {
Map<String, Object> map = new LinkedHashMap<>();
List<ObjectField> objectFields = objectValue.getObjectFields();
for (ObjectField objectField : objectFields) {
String objectFieldName = objectField.getName();
Value<?> objectFieldValue = objectField.getValue();
map.put(objectFieldName, toVariableValue(objectFieldValue));
}
return map;
}

@NotNull
private static List<Object> toVariableValues(List<Value> arrayValues) {
// some values can be null (NullValue) and hence we can use Immutable Lists
return arrayValues.stream()
.map(ValueToVariableValueCompiler::toVariableValue)
.collect(toList());
Copy link
Contributor

Choose a reason for hiding this comment

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

Should you use FpKit?

}

@Nullable
private static Object toVariableValue(Value<?> value) {
if (value instanceof ObjectValue) {
return toVariableValue((ObjectValue) value);
} else if (value instanceof ArrayValue) {
return toVariableValues(((ArrayValue) value).getValues());
} else if (value instanceof StringValue) {
return ((StringValue) value).getValue();
} else if (value instanceof FloatValue) {
return ((FloatValue) value).getValue();
} else if (value instanceof IntValue) {
return ((IntValue) value).getValue();
} else if (value instanceof BooleanValue) {
return ((BooleanValue) value).isValue();
} else if (value instanceof NullValue) {
return null;
}
throw new AssertException("Should never happen. Cannot handle node of type: " + value.getClass());
}

private static String getVarName(int variableOrdinal) {
return "v" + variableOrdinal;
}
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 shortened the original work that Artyom put in as var_0 - I figure we should be as short as possible


}
59 changes: 59 additions & 0 deletions src/main/java/graphql/normalized/VariableAccumulator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package graphql.normalized;

import graphql.Internal;
import graphql.language.VariableDefinition;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static graphql.normalized.ValueToVariableValueCompiler.normalizedInputValueToVariable;

/**
* This accumulator class decides on whether to create a variable for a query argument and if so it tracks what variables were made.
* The {@link ExecutableNormalizedOperationToAstCompiler} then uses all the variables when it compiles the final document.
*/
@Internal
public class VariableAccumulator {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice


private final List<VariableValueWithDefinition> valueWithDefinitions;
private final VariablePredicate variablePredicate;

public VariableAccumulator(VariablePredicate variablePredicate) {
this.variablePredicate = variablePredicate;
valueWithDefinitions = new ArrayList<>();
}

public boolean shouldMakeVariable(ExecutableNormalizedField executableNormalizedField, String argName, NormalizedInputValue normalizedInputValue) {
return variablePredicate.shouldMakeVariable(executableNormalizedField, argName, normalizedInputValue);
}

public VariableValueWithDefinition accumulateVariable(NormalizedInputValue normalizedInputValue) {
VariableValueWithDefinition variableWithDefinition = normalizedInputValueToVariable(normalizedInputValue, getAccumulatedSize());
valueWithDefinitions.add(variableWithDefinition);
return variableWithDefinition;
}

public int getAccumulatedSize() {
return valueWithDefinitions.size();
}

/**
* @return the variable definitions that would go on the operation declaration
*/
public List<VariableDefinition> getVariableDefinitions() {
return valueWithDefinitions.stream().map(VariableValueWithDefinition::getDefinition).collect(Collectors.toList());
}

/**
* @return the map of variable names to variable values
*/
public Map<String, Object> getVariablesMap() {
return valueWithDefinitions.stream()
Copy link
Contributor

Choose a reason for hiding this comment

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

No immutable collections stuff?

.collect(Collectors.toMap(
variableWithDefinition -> variableWithDefinition.getDefinition().getName(),
VariableValueWithDefinition::getValue
));
}
}
19 changes: 19 additions & 0 deletions src/main/java/graphql/normalized/VariablePredicate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package graphql.normalized;

import graphql.Internal;

/**
* This predicate indicates whether a variable should be made for this field argument
*/
@Internal
Copy link
Contributor

Choose a reason for hiding this comment

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

Internal?

public interface VariablePredicate {
/**
* Return true if a variable should be made for this field argument
*
* @param executableNormalizedField the field in question
* @param argName the argument on the field
* @param normalizedInputValue the input value for that argument
* @return true if a variable should be made
*/
boolean shouldMakeVariable(ExecutableNormalizedField executableNormalizedField, String argName, NormalizedInputValue normalizedInputValue);
}
Loading