Skip to content

Commit b94cbe7

Browse files
samuelAndalonSamuel Vazquez
and
Samuel Vazquez
authored
feat(6.x.x): SingletonPropertyDataFetcher, avoid allocating a PropertyDataFetcher per property per graphql operation (#2084)
### 📝 Description cherry-pick #2079 --------- Co-authored-by: Samuel Vazquez <[email protected]>
1 parent ea053a8 commit b94cbe7

34 files changed

+246
-138
lines changed

.github/workflows/continuous-integration.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
run: ./gradlew clean build
3333

3434
- name: Archive failure build reports
35-
uses: actions/upload-artifact@v3
35+
uses: actions/upload-artifact@v4
3636
if: failure()
3737
with:
3838
name: build-reports
@@ -47,7 +47,7 @@ jobs:
4747
run: ./gradlew clean build
4848

4949
- name: Archive examples failure build reports
50-
uses: actions/upload-artifact@v3
50+
uses: actions/upload-artifact@v4
5151
if: failure()
5252
with:
5353
name: build-examples-reports

.github/workflows/pr-check-docs.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
with:
2222
node-version: 16
2323

24-
- uses: actions/cache@v2
24+
- uses: actions/cache@v3
2525
with:
2626
path: ~/.npm
2727
key: ${{ runner.os }}-node-${{ hashFiles('website/package-lock.json') }}

.github/workflows/pr-check.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131

3232
# Used by maven-plugin integration tests
3333
- name: Set up Maven cache
34-
uses: actions/cache@v2
34+
uses: actions/cache@v3
3535
with:
3636
path: ~/.m2/repository
3737
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
@@ -41,7 +41,7 @@ jobs:
4141
run: ./gradlew clean build
4242

4343
- name: Archive failure build reports
44-
uses: actions/upload-artifact@v3
44+
uses: actions/upload-artifact@v4
4545
if: failure()
4646
with:
4747
name: build-reports
@@ -56,7 +56,7 @@ jobs:
5656
run: ./gradlew clean build
5757

5858
- name: Archive examples failure build reports
59-
uses: actions/upload-artifact@v3
59+
uses: actions/upload-artifact@v4
6060
if: failure()
6161
with:
6262
name: build-examples-reports

.github/workflows/publish-latest-docs.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
with:
2222
node-version: 16
2323

24-
- uses: actions/cache@v2
24+
- uses: actions/cache@v3
2525
with:
2626
path: ~/.npm
2727
key: ${{ runner.os }}-node-${{ hashFiles('website/package-lock.json') }}

.github/workflows/release-code.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
PLUGIN_PORTAL_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }}
3939

4040
- name: Archive failure build reports
41-
uses: actions/upload-artifact@v3
41+
uses: actions/upload-artifact@v4
4242
if: failure()
4343
with:
4444
name: build-reports

generator/graphql-kotlin-schema-generator/build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ tasks {
2828
limit {
2929
counter = "BRANCH"
3030
value = "COVEREDRATIO"
31-
minimum = "0.93".toBigDecimal()
31+
minimum = "0.91".toBigDecimal()
3232
}
3333
}
3434
}

generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/execution/KotlinDataFetcherFactoryProvider.kt

+9
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,12 @@ open class SimpleKotlinDataFetcherFactoryProvider : KotlinDataFetcherFactoryProv
6262
PropertyDataFetcher(kProperty.getter)
6363
}
6464
}
65+
66+
/**
67+
* [SimpleSingletonKotlinDataFetcherFactoryProvider] is a specialization of [SimpleKotlinDataFetcherFactoryProvider] that will provide a
68+
* a [SingletonPropertyDataFetcher] that should be used to target property resolutions without allocating a DataFetcher per property
69+
*/
70+
open class SimpleSingletonKotlinDataFetcherFactoryProvider : SimpleKotlinDataFetcherFactoryProvider() {
71+
override fun propertyDataFetcherFactory(kClass: KClass<*>, kProperty: KProperty<*>): DataFetcherFactory<Any?> =
72+
SingletonPropertyDataFetcher.getFactoryAndRegister(kClass, kProperty)
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.expediagroup.graphql.generator.execution
2+
3+
import graphql.schema.DataFetcher
4+
import graphql.schema.DataFetcherFactory
5+
import graphql.schema.DataFetchingEnvironment
6+
import graphql.schema.GraphQLFieldDefinition
7+
import graphql.schema.LightDataFetcher
8+
import java.util.concurrent.ConcurrentHashMap
9+
import java.util.function.Supplier
10+
import kotlin.reflect.KClass
11+
import kotlin.reflect.KProperty
12+
13+
/**
14+
* Singleton Property [DataFetcher] that stores references to underlying properties getters.
15+
*/
16+
internal object SingletonPropertyDataFetcher : LightDataFetcher<Any?> {
17+
18+
private val factory: DataFetcherFactory<Any?> = DataFetcherFactory<Any?> { SingletonPropertyDataFetcher }
19+
20+
private val getters: ConcurrentHashMap<String, KProperty.Getter<*>> = ConcurrentHashMap()
21+
22+
fun getFactoryAndRegister(kClass: KClass<*>, kProperty: KProperty<*>): DataFetcherFactory<Any?> {
23+
getters.computeIfAbsent("${kClass.java.name}.${kProperty.name}") {
24+
kProperty.getter
25+
}
26+
return factory
27+
}
28+
29+
override fun get(
30+
fieldDefinition: GraphQLFieldDefinition,
31+
sourceObject: Any?,
32+
environmentSupplier: Supplier<DataFetchingEnvironment>
33+
): Any? =
34+
sourceObject?.let {
35+
getters["${sourceObject.javaClass.name}.${fieldDefinition.name}"]?.call(sourceObject)
36+
}
37+
38+
override fun get(environment: DataFetchingEnvironment): Any? =
39+
environment.getSource<Any?>()?.let { sourceObject ->
40+
getters["${sourceObject.javaClass.name}.${environment.fieldDefinition.name}"]?.call(sourceObject)
41+
}
42+
}

generator/graphql-kotlin-schema-generator/src/test/kotlin/com/expediagroup/graphql/generator/PolymorphicTests.kt

+11-11
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class PolymorphicTests {
3737

3838
@Test
3939
fun `Schema generator creates union types from marked up interface`() {
40-
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithUnion())), config = testSchemaConfig)
40+
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithUnion())), config = testSchemaConfig())
4141

4242
val graphqlType = schema.getType("BodyPart") as? GraphQLUnionType
4343
assertNotNull(graphqlType)
@@ -54,7 +54,7 @@ class PolymorphicTests {
5454

5555
@Test
5656
fun `SchemaGenerator can expose an interface and its implementations`() {
57-
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithInterface())), config = testSchemaConfig)
57+
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithInterface())), config = testSchemaConfig())
5858

5959
val interfaceType = schema.getType("AnInterface") as? GraphQLInterfaceType
6060
assertNotNull(interfaceType)
@@ -68,20 +68,20 @@ class PolymorphicTests {
6868
@Test
6969
fun `Interfaces cannot be used as input field types`() {
7070
assertThrows(InvalidInputFieldTypeException::class.java) {
71-
toSchema(queries = listOf(TopLevelObject(QueryWithUnAuthorizedInterfaceArgument())), config = testSchemaConfig)
71+
toSchema(queries = listOf(TopLevelObject(QueryWithUnAuthorizedInterfaceArgument())), config = testSchemaConfig())
7272
}
7373
}
7474

7575
@Test
7676
fun `Union cannot be used as input field types`() {
7777
assertThrows(InvalidInputFieldTypeException::class.java) {
78-
toSchema(queries = listOf(TopLevelObject(QueryWithUnAuthorizedUnionArgument())), config = testSchemaConfig)
78+
toSchema(queries = listOf(TopLevelObject(QueryWithUnAuthorizedUnionArgument())), config = testSchemaConfig())
7979
}
8080
}
8181

8282
@Test
8383
fun `Object types implementing union and interfaces are only created once`() {
84-
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithInterfaceAndUnion())), config = testSchemaConfig)
84+
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithInterfaceAndUnion())), config = testSchemaConfig())
8585

8686
val carType = schema.getType("Car") as? GraphQLObjectType
8787
assertNotNull(carType)
@@ -95,15 +95,15 @@ class PolymorphicTests {
9595

9696
@Test
9797
fun `Interfaces can declare properties of their own type`() {
98-
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithRecursiveType())), config = testSchemaConfig)
98+
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithRecursiveType())), config = testSchemaConfig())
9999

100100
val personType = schema.getType("Person")
101101
assertNotNull(personType)
102102
}
103103

104104
@Test
105105
fun `Abstract classes should be converted to interfaces`() {
106-
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithAbstract())), config = testSchemaConfig)
106+
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithAbstract())), config = testSchemaConfig())
107107

108108
val abstractInterface = schema.getType("MyAbstract") as? GraphQLInterfaceType
109109
assertNotNull(abstractInterface)
@@ -116,7 +116,7 @@ class PolymorphicTests {
116116

117117
@Test
118118
fun `Interface types can be correctly resolved`() {
119-
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithRenamedAbstracts())), config = testSchemaConfig)
119+
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithRenamedAbstracts())), config = testSchemaConfig())
120120

121121
val cakeInterface = schema.getType("Cake") as? GraphQLInterfaceType
122122
assertNotNull(cakeInterface)
@@ -132,7 +132,7 @@ class PolymorphicTests {
132132

133133
@Test
134134
fun `Union types can be correctly resolved`() {
135-
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithRenamedAbstracts())), config = testSchemaConfig)
135+
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithRenamedAbstracts())), config = testSchemaConfig())
136136

137137
val dessertUnion = schema.getType("Dessert") as? GraphQLUnionType
138138
assertNotNull(dessertUnion)
@@ -148,7 +148,7 @@ class PolymorphicTests {
148148

149149
@Test
150150
fun `Interface implementations are not computed when marked with GraphQLIgnore annotation`() {
151-
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithIgnoredInfo())), config = testSchemaConfig)
151+
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithIgnoredInfo())), config = testSchemaConfig())
152152
val service = schema.getType("Service") as? GraphQLInterfaceType
153153
assertNotNull(service)
154154

@@ -161,7 +161,7 @@ class PolymorphicTests {
161161

162162
@Test
163163
fun `Ignored interface properties should not appear in the subtype`() {
164-
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithIgnoredInfo())), config = testSchemaConfig)
164+
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithIgnoredInfo())), config = testSchemaConfig())
165165
val service = schema.getType("Service") as? GraphQLInterfaceType
166166
assertNotNull(service)
167167
val interfaceIgnoredField = service.getFieldDefinition("shouldNotBeInTheSchema")

generator/graphql-kotlin-schema-generator/src/test/kotlin/com/expediagroup/graphql/generator/SchemaGeneratorAsyncTests.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class SchemaGeneratorAsyncTests {
4242

4343
@Test
4444
fun `SchemaGenerator strips type argument from CompletableFuture to support async servlet`() {
45-
val schema = toSchema(queries = listOf(TopLevelObject(AsyncQuery())), config = testSchemaConfig)
45+
val schema = toSchema(queries = listOf(TopLevelObject(AsyncQuery())), config = testSchemaConfig())
4646
val returnType =
4747
(schema.getObjectType("Query").getFieldDefinition("asynchronouslyDo").type as? GraphQLNonNull)?.wrappedType
4848
assertNotNull(returnType)

0 commit comments

Comments
 (0)