Repository navigation
Singleton property data fetcher #3754
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
Changes from 1 commit
689e401
a5a22c0
0d80213
612084e
b8bfd4b
70bac09
b8b2e26
424cac7
d1b132a
886d745
9ca4d30
b13b7c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,35 @@ | |
| @PublicApi | ||
| public class PropertyDataFetcher<T> implements LightDataFetcher<T> { | ||
|
|
||
| private static final PropertyDataFetcher<Object> SINGLETON_FETCHER = new PropertyDataFetcher<>() { | ||
| @Override | ||
| Object fetchImpl(String propertyName, Object source, GraphQLFieldDefinition fieldDefinition, Supplier<DataFetchingEnvironment> environmentSupplier) { | ||
| return super.fetchImpl(fieldDefinition.getName(), source, fieldDefinition, environmentSupplier); | ||
| } | ||
| }; | ||
|
|
||
| private static final DataFetcherFactory<?> SINGLETON_FETCHER_FACTORY = environment -> SINGLETON_FETCHER; | ||
|
|
||
| /** | ||
| * This returns the same singleton {@link PropertyDataFetcher} that fetches property values | ||
| * based on the name of the field that iis passed into it. | ||
| * | ||
| * @return a singleton property data fetcher | ||
| */ | ||
| public static PropertyDataFetcher<?> singleton() { | ||
| return SINGLETON_FETCHER; | ||
| } | ||
|
|
||
| /** | ||
| * This returns the same singleton {@link DataFetcherFactory} that returns the value of {@link #singleton()} | ||
| * | ||
| * @return a singleton data fetcher factory | ||
| */ | ||
| public static DataFetcherFactory<?> singletonFactory() { | ||
| return SINGLETON_FETCHER_FACTORY; | ||
| } | ||
|
|
||
|
|
||
| private final String propertyName; | ||
| private final Function<Object, Object> function; | ||
|
|
||
|
|
@@ -53,6 +82,11 @@ private <O> PropertyDataFetcher(Function<O, T> function) { | |
| this.propertyName = null; | ||
| } | ||
|
|
||
| private PropertyDataFetcher() { | ||
| this.function = null; | ||
| this.propertyName = null; | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we no longer need the property name but its kept allow older code to work that might use direct names or functions |
||
|
|
||
| /** | ||
| * Returns a data fetcher that will use the property name to examine the {@link DataFetchingEnvironment#getSource()} object | ||
| * for a getter method or field with that name, or if it's a map, it will look up a value using | ||
|
|
@@ -109,17 +143,26 @@ public String getPropertyName() { | |
|
|
||
| @Override | ||
| public T get(GraphQLFieldDefinition fieldDefinition, Object source, Supplier<DataFetchingEnvironment> environmentSupplier) throws Exception { | ||
| return getImpl(source, fieldDefinition.getType(), environmentSupplier); | ||
| return fetchImpl(propertyName, source, fieldDefinition, environmentSupplier); | ||
| } | ||
|
|
||
| @Override | ||
| public T get(DataFetchingEnvironment environment) { | ||
| Object source = environment.getSource(); | ||
| return getImpl(source, environment.getFieldType(), () -> environment); | ||
| return fetchImpl(propertyName, environment.getSource(), environment.getFieldDefinition(), () -> environment); | ||
| } | ||
|
|
||
| /** | ||
| * This is our implementation of property fetching | ||
| * | ||
| * @param propertyName the name of the property to fetch in the source object | ||
| * @param source the source object to fetch from | ||
| * @param fieldDefinition the field definition of the field being fetched for | ||
| * @param environmentSupplier a supplied of thee {@link DataFetchingEnvironment} | ||
| * | ||
| * @return a value of type T | ||
| */ | ||
| @SuppressWarnings("unchecked") | ||
| private T getImpl(Object source, GraphQLOutputType fieldDefinition, Supplier<DataFetchingEnvironment> environmentSupplier) { | ||
| T fetchImpl(String propertyName, Object source, GraphQLFieldDefinition fieldDefinition, Supplier<DataFetchingEnvironment> environmentSupplier) { | ||
| if (source == null) { | ||
| return null; | ||
| } | ||
|
|
@@ -128,7 +171,7 @@ private T getImpl(Object source, GraphQLOutputType fieldDefinition, Supplier<Dat | |
| return (T) function.apply(source); | ||
| } | ||
|
|
||
| return (T) PropertyDataFetcherHelper.getPropertyValue(propertyName, source, fieldDefinition, environmentSupplier); | ||
| return (T) PropertyDataFetcherHelper.getPropertyValue(propertyName, source, fieldDefinition.getType(), environmentSupplier); | ||
| } | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| package graphql | ||
|
|
||
|
|
||
| import graphql.schema.GraphQLFieldDefinition | ||
| import graphql.schema.GraphQLOutputType | ||
| import graphql.schema.PropertyDataFetcher | ||
| import spock.lang.Specification | ||
|
|
@@ -55,43 +56,64 @@ class DataFetcherTest extends Specification { | |
|
|
||
| } | ||
|
|
||
| def env(GraphQLOutputType type) { | ||
| newDataFetchingEnvironment().source(dataHolder).fieldType(type).build() | ||
| def mkDFE(String propertyName, GraphQLOutputType type) { | ||
| def fieldDefinition = GraphQLFieldDefinition.newFieldDefinition().name(propertyName).type(type).build() | ||
| newDataFetchingEnvironment().source(dataHolder).fieldType(type).fieldDefinition(fieldDefinition).build() | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a valid DataFetchingEnvironment will always have a field definition |
||
| } | ||
|
|
||
| def "get property value"() { | ||
| given: | ||
| def environment = env(GraphQLString) | ||
| def environment = mkDFE("property", GraphQLString) | ||
| when: | ||
| def result = new PropertyDataFetcher("property").get(environment) | ||
| def result = fetcher.get(environment) | ||
| then: | ||
| result == "propertyValue" | ||
|
|
||
| where: | ||
| fetcher | _ | ||
| new PropertyDataFetcher("property") | _ | ||
| PropertyDataFetcher.singleton() | _ | ||
| } | ||
|
|
||
| def "get Boolean property value"() { | ||
| given: | ||
| def environment = env(GraphQLBoolean) | ||
| def environment = mkDFE("booleanField", GraphQLBoolean) | ||
| when: | ||
| def result = new PropertyDataFetcher("booleanField").get(environment) | ||
| def result = fetcher.get(environment) | ||
| then: | ||
| result == true | ||
|
|
||
| where: | ||
| fetcher | _ | ||
| new PropertyDataFetcher("booleanField") | _ | ||
| PropertyDataFetcher.singleton() | _ | ||
| } | ||
|
|
||
| def "get Boolean property value with get"() { | ||
| given: | ||
| def environment = env(GraphQLBoolean) | ||
| def environment = mkDFE("booleanFieldWithGet", GraphQLBoolean) | ||
| when: | ||
| def result = new PropertyDataFetcher("booleanFieldWithGet").get(environment) | ||
| def result = fetcher.get(environment) | ||
| then: | ||
| result == false | ||
|
|
||
| where: | ||
| fetcher | _ | ||
| new PropertyDataFetcher("booleanFieldWithGet") | _ | ||
| PropertyDataFetcher.singleton() | _ | ||
| } | ||
|
|
||
| def "get public field value as property"() { | ||
| given: | ||
| def environment = env(GraphQLString) | ||
| def environment = mkDFE("publicField", GraphQLString) | ||
| when: | ||
| def result = new PropertyDataFetcher("publicField").get(environment) | ||
| def result = fetcher.get(environment) | ||
| then: | ||
| result == "publicValue" | ||
|
|
||
| where: | ||
| fetcher | _ | ||
| new PropertyDataFetcher("publicField") | _ | ||
| PropertyDataFetcher.singleton() | _ | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package graphql | ||
|
|
||
|
|
||
| import graphql.schema.FieldCoordinates | ||
| import graphql.schema.GraphQLFieldDefinition | ||
| import graphql.schema.PropertyDataFetcher | ||
| import spock.lang.Specification | ||
|
|
||
| class LargeSchemaDataFetcherTest extends Specification { | ||
|
|
||
| def howManyFields = 100_000 | ||
|
|
||
| def "large schema with lots of fields has a property data fetcher by default"() { | ||
| def sdl = """ | ||
| type Query { | ||
| ${mkFields()} | ||
| } | ||
| """ | ||
|
|
||
| when: | ||
| def schema = TestUtil.schema(sdl) | ||
| def codeRegistry = schema.getCodeRegistry() | ||
|
|
||
| then: | ||
|
|
||
| for (int i = 0; i < howManyFields; i++) { | ||
| def fieldName = "f$i" | ||
| def fieldDef = GraphQLFieldDefinition.newFieldDefinition().name(fieldName).type(Scalars.GraphQLString).build() | ||
| def df = codeRegistry.getDataFetcher(FieldCoordinates.coordinates("Query", fieldName), fieldDef) | ||
|
|
||
| // in the future we hope to make this the same DF instance | ||
| assert df instanceof PropertyDataFetcher | ||
| } | ||
| } | ||
|
|
||
| def mkFields() { | ||
| StringBuilder sb = new StringBuilder() | ||
| for (int i = 0; i < howManyFields; i++) { | ||
| sb.append("f$i : String\n") | ||
| } | ||
| return sb.toString() | ||
| } | ||
| } |
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 causes the same instance of the "default" DF to be used