diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentation.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentation.kt index 2a4662dc59..00b88c36a1 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentation.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentation.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,9 +37,8 @@ import org.dataloader.DataLoader class DataLoaderLevelDispatchedInstrumentation : AbstractExecutionLevelDispatchedInstrumentation() { override fun getOnLevelDispatchedCallback( parameters: ExecutionLevelDispatchedInstrumentationParameters - ): OnLevelDispatchedCallback = { _, executions: List -> - executions - .getOrNull(0) + ): OnLevelDispatchedCallback = { _, _ -> + parameters.executionContext.executionInput ?.dataLoaderRegistry ?.dispatchAll() } diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/execution/AbstractExecutionLevelDispatchedInstrumentation.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/execution/AbstractExecutionLevelDispatchedInstrumentation.kt index 0f4423d733..dfef0e2287 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/execution/AbstractExecutionLevelDispatchedInstrumentation.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/execution/AbstractExecutionLevelDispatchedInstrumentation.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,11 +22,12 @@ import com.expediagroup.graphql.dataloader.instrumentation.level.state.Level import graphql.ExecutionInput import graphql.ExecutionResult import graphql.execution.ExecutionContext +import graphql.execution.ExecutionId import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext import graphql.execution.instrumentation.InstrumentationContext import graphql.execution.instrumentation.InstrumentationState import graphql.execution.instrumentation.SimplePerformantInstrumentation -import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters +import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters import graphql.schema.DataFetcher @@ -34,7 +35,7 @@ import graphql.schema.DataFetcher /** * Represents the signature of a callback that will be executed when a [Level] is dispatched */ -internal typealias OnLevelDispatchedCallback = (Level, List) -> Unit +internal typealias OnLevelDispatchedCallback = (Level, List) -> Unit /** * Custom GraphQL [graphql.execution.instrumentation.Instrumentation] that calculate the state of executions * of all queries sharing the same GraphQLContext map @@ -52,13 +53,13 @@ abstract class AbstractExecutionLevelDispatchedInstrumentation : SimplePerforman parameters: ExecutionLevelDispatchedInstrumentationParameters ): OnLevelDispatchedCallback - override fun beginExecuteOperation( - parameters: InstrumentationExecuteOperationParameters, + override fun beginExecution( + parameters: InstrumentationExecutionParameters, state: InstrumentationState? ): InstrumentationContext? = - parameters.executionContext.takeUnless(ExecutionContext::isMutation) + parameters.executionInput ?.graphQLContext?.get(ExecutionLevelDispatchedState::class) - ?.beginExecuteOperation(parameters) + ?.beginExecution(parameters) override fun beginExecutionStrategy( parameters: InstrumentationExecutionStrategyParameters, diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ExecutionLevelDispatchedState.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ExecutionLevelDispatchedState.kt index 8a6742fed4..db93a0b514 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ExecutionLevelDispatchedState.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/state/ExecutionLevelDispatchedState.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,24 +20,41 @@ import com.expediagroup.graphql.dataloader.instrumentation.extensions.getExpecte import com.expediagroup.graphql.dataloader.instrumentation.level.execution.OnLevelDispatchedCallback import graphql.ExecutionInput import graphql.ExecutionResult +import graphql.execution.ExecutionId import graphql.execution.FieldValueInfo import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext import graphql.execution.instrumentation.InstrumentationContext -import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters +import graphql.execution.instrumentation.SimpleInstrumentationContext +import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters import graphql.schema.DataFetcher import java.util.concurrent.CompletableFuture import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicReference /** * Orchestrate the [ExecutionBatchState] of all [ExecutionInput] sharing the same graphQLContext map, * when a certain state is reached will invoke [OnLevelDispatchedCallback] */ class ExecutionLevelDispatchedState( - private val totalExecutions: Int + totalOperations: Int ) { - val executions = ConcurrentHashMap() + private val totalExecutions: AtomicReference = AtomicReference(totalOperations) + val executions = ConcurrentHashMap() + + /** + * Remove an [ExecutionBatchState] from the state in case operation does not qualify for execution, + * for example: + * parsing, validation, execution errors + * persisted query errors + */ + private fun removeExecution(executionId: ExecutionId) { + if (executions.containsKey(executionId)) { + executions.remove(executionId) + totalExecutions.set(totalExecutions.get() - 1) + } + } /** * Initialize the [ExecutionBatchState] of this [ExecutionInput] @@ -45,13 +62,21 @@ class ExecutionLevelDispatchedState( * @param parameters contains information of which [ExecutionInput] will start his execution * @return a nullable [InstrumentationContext] */ - fun beginExecuteOperation( - parameters: InstrumentationExecuteOperationParameters - ): InstrumentationContext? { - executions.computeIfAbsent(parameters.executionContext.executionInput) { + fun beginExecution( + parameters: InstrumentationExecutionParameters + ): InstrumentationContext { + executions.computeIfAbsent(parameters.executionInput.executionId) { ExecutionBatchState() } - return null + return object : SimpleInstrumentationContext() { + override fun onCompleted(result: ExecutionResult?, t: Throwable?) { + result?.let { + if (result.errors.size > 0) { + removeExecution(parameters.executionInput.executionId) + } + } + } + } } /** @@ -64,11 +89,11 @@ class ExecutionLevelDispatchedState( parameters: InstrumentationExecutionStrategyParameters, onLevelDispatched: OnLevelDispatchedCallback ): ExecutionStrategyInstrumentationContext { - val executionInput = parameters.executionContext.executionInput + val executionId = parameters.executionContext.executionInput.executionId val level = Level(parameters.executionStrategyParameters.path.level + 1) val fieldCount = parameters.executionStrategyParameters.fields.size() - executions.computeIfPresent(executionInput) { _, executionState -> + executions.computeIfPresent(executionId) { _, executionState -> executionState.also { it.initializeLevelStateIfNeeded(level) it.increaseExpectedFetches(level, fieldCount) @@ -86,7 +111,7 @@ class ExecutionLevelDispatchedState( override fun onFieldValuesInfo(fieldValueInfoList: List) { val nextLevel = level.next() - executions.computeIfPresent(executionInput) { _, executionState -> + executions.computeIfPresent(executionId) { _, executionState -> executionState.also { it.increaseOnFieldValueInfos(level) it.increaseExpectedExecutionStrategies( @@ -104,7 +129,7 @@ class ExecutionLevelDispatchedState( } override fun onFieldValuesException() { - executions.computeIfPresent(executionInput) { _, executionState -> + executions.computeIfPresent(executionId) { _, executionState -> executionState.also { it.increaseOnFieldValueInfos(level) } @@ -123,14 +148,13 @@ class ExecutionLevelDispatchedState( parameters: InstrumentationFieldFetchParameters, onLevelDispatched: OnLevelDispatchedCallback ): InstrumentationContext { - val executionInput = parameters.executionContext.executionInput + val executionId = parameters.executionContext.executionInput.executionId val path = parameters.executionStepInfo.path val level = Level(path.level) - return object : InstrumentationContext { + return object : SimpleInstrumentationContext() { override fun onDispatched(result: CompletableFuture) { - - executions.computeIfPresent(executionInput) { _, executionState -> + executions.computeIfPresent(executionId) { _, executionState -> executionState.also { it.increaseDispatchedFetches(level) } } @@ -140,9 +164,6 @@ class ExecutionLevelDispatchedState( executions.forEach { (_, executionState) -> executionState.completeDataFetchers(level) } } } - - override fun onCompleted(result: Any?, t: Throwable?) { - } } } @@ -161,7 +182,7 @@ class ExecutionLevelDispatchedState( parameters: InstrumentationFieldFetchParameters ): DataFetcher<*> { var manuallyCompletableDataFetcher: DataFetcher<*> = dataFetcher - executions.computeIfPresent(parameters.executionContext.executionInput) { _, executionState -> + executions.computeIfPresent(parameters.executionContext.executionInput.executionId) { _, executionState -> executionState.also { manuallyCompletableDataFetcher = it.toManuallyCompletableDataFetcher( Level(parameters.executionStepInfo.path.level), @@ -180,9 +201,11 @@ class ExecutionLevelDispatchedState( * @param level that execution state will be calculated * @return Boolean for allExecutionsDispatched statement */ - fun allExecutionsDispatched(level: Level): Boolean = - executions - .takeIf { executions -> executions.size == totalExecutions } - ?.all { (_, executionState) -> executionState.isLevelDispatched(level) } - ?: false + fun allExecutionsDispatched(level: Level): Boolean = synchronized(executions) { + val operationsToExecute = totalExecutions.get() + when { + executions.size < operationsToExecute -> false + else -> executions.all { (_, executionState) -> executionState.isLevelDispatched(level) } + } + } } diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/DataLoaderSyncExecutionExhaustedInstrumentation.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/DataLoaderSyncExecutionExhaustedInstrumentation.kt index cbdc6141a5..209b61bcbb 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/DataLoaderSyncExecutionExhaustedInstrumentation.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/DataLoaderSyncExecutionExhaustedInstrumentation.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.execut import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.execution.SyncExecutionExhaustedInstrumentationParameters import graphql.ExecutionInput import graphql.GraphQLContext +import graphql.execution.ExecutionId import graphql.execution.instrumentation.Instrumentation import graphql.schema.DataFetcher import org.dataloader.DataLoader @@ -37,10 +38,10 @@ import java.util.concurrent.CompletableFuture class DataLoaderSyncExecutionExhaustedInstrumentation : AbstractSyncExecutionExhaustedInstrumentation() { override fun getOnSyncExecutionExhaustedCallback( parameters: SyncExecutionExhaustedInstrumentationParameters - ): OnSyncExecutionExhaustedCallback = { executions: List -> - executions - .getOrNull(0) - ?.dataLoaderRegistry - ?.dispatchAll() + ): OnSyncExecutionExhaustedCallback = { _: List -> + parameters + .executionContext.executionInput + .dataLoaderRegistry + .dispatchAll() } } diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/execution/AbstractSyncExecutionExhaustedInstrumentation.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/execution/AbstractSyncExecutionExhaustedInstrumentation.kt index 6d93ce48f5..8d06df8f85 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/execution/AbstractSyncExecutionExhaustedInstrumentation.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/execution/AbstractSyncExecutionExhaustedInstrumentation.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,19 +22,20 @@ import graphql.ExecutionInput import graphql.ExecutionResult import graphql.GraphQLContext import graphql.execution.ExecutionContext +import graphql.execution.ExecutionId import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext import graphql.execution.instrumentation.Instrumentation import graphql.execution.instrumentation.InstrumentationContext import graphql.execution.instrumentation.InstrumentationState import graphql.execution.instrumentation.SimplePerformantInstrumentation -import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters +import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters /** * typealias that represents the signature of a callback that will be executed when sync execution is exhausted */ -internal typealias OnSyncExecutionExhaustedCallback = (List) -> Unit +internal typealias OnSyncExecutionExhaustedCallback = (List) -> Unit /** * Custom GraphQL [Instrumentation] that calculate the synchronous execution exhaustion @@ -52,13 +53,13 @@ abstract class AbstractSyncExecutionExhaustedInstrumentation : SimplePerformantI parameters: SyncExecutionExhaustedInstrumentationParameters ): OnSyncExecutionExhaustedCallback - override fun beginExecuteOperation( - parameters: InstrumentationExecuteOperationParameters, + override fun beginExecution( + parameters: InstrumentationExecutionParameters, state: InstrumentationState? ): InstrumentationContext? = - parameters.executionContext.takeUnless(ExecutionContext::isMutation) - ?.graphQLContext?.get(SyncExecutionExhaustedState::class) - ?.beginExecuteOperation(parameters) + parameters.graphQLContext + ?.get(SyncExecutionExhaustedState::class) + ?.beginExecution(parameters) override fun beginExecutionStrategy( parameters: InstrumentationExecutionStrategyParameters, diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/SyncExecutionExhaustedState.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/SyncExecutionExhaustedState.kt index 6ba1c22fa2..14d1237585 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/SyncExecutionExhaustedState.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/SyncExecutionExhaustedState.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,25 +21,43 @@ import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.execut import graphql.ExecutionInput import graphql.ExecutionResult import graphql.GraphQLContext +import graphql.execution.ExecutionId import graphql.execution.MergedField import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext import graphql.execution.instrumentation.InstrumentationContext -import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters +import graphql.execution.instrumentation.SimpleInstrumentationContext +import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters import graphql.schema.DataFetcher import java.util.concurrent.CompletableFuture import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicReference /** * Orchestrate the [ExecutionBatchState] of all [ExecutionInput] sharing the same [GraphQLContext], * when a certain state is reached will invoke [OnSyncExecutionExhaustedCallback] */ class SyncExecutionExhaustedState( - private val totalExecutions: Int, + totalOperations: Int, private val dataLoaderRegistry: KotlinDataLoaderRegistry ) { - val executions = ConcurrentHashMap() + + private val totalExecutions: AtomicReference = AtomicReference(totalOperations) + val executions = ConcurrentHashMap() + + /** + * Remove an [ExecutionBatchState] from the state in case operation does not qualify for starting an execution, + * for example: + * - parsing, validation errors + * - persisted query errors + */ + private fun removeExecution(executionId: ExecutionId) { + if (executions.containsKey(executionId)) { + executions.remove(executionId) + totalExecutions.set(totalExecutions.get() - 1) + } + } /** * Create the [ExecutionBatchState] When a specific [ExecutionInput] starts his execution @@ -47,13 +65,21 @@ class SyncExecutionExhaustedState( * @param parameters contains information of which [ExecutionInput] will start his execution * @return a non null [InstrumentationContext] object */ - fun beginExecuteOperation( - parameters: InstrumentationExecuteOperationParameters - ): InstrumentationContext? { - executions.computeIfAbsent(parameters.executionContext.executionInput) { + fun beginExecution( + parameters: InstrumentationExecutionParameters + ): InstrumentationContext { + executions.computeIfAbsent(parameters.executionInput.executionId) { ExecutionBatchState() } - return null + return object : SimpleInstrumentationContext() { + override fun onCompleted(result: ExecutionResult?, t: Throwable?) { + result?.let { + if (result.errors.size > 0) { + removeExecution(parameters.executionInput.executionId) + } + } + } + } } /** @@ -66,9 +92,9 @@ class SyncExecutionExhaustedState( fun beginExecutionStrategy( parameters: InstrumentationExecutionStrategyParameters ): ExecutionStrategyInstrumentationContext? { - val executionInput = parameters.executionContext.executionInput + val executionId = parameters.executionContext.executionInput.executionId - executions.computeIfPresent(executionInput) { _, executionState -> + executions.computeIfPresent(executionId) { _, executionState -> val executionStrategyParameters = parameters.executionStrategyParameters val field = executionStrategyParameters.field?.singleField @@ -95,14 +121,14 @@ class SyncExecutionExhaustedState( parameters: InstrumentationFieldFetchParameters, onSyncExecutionExhausted: OnSyncExecutionExhaustedCallback ): InstrumentationContext { - val executionInput = parameters.executionContext.executionInput + val executionId = parameters.executionContext.executionInput.executionId val field = parameters.executionStepInfo.field.singleField val fieldExecutionStrategyPath = parameters.executionStepInfo.path.parent val fieldGraphQLType = parameters.executionStepInfo.unwrappedNonNullType return object : InstrumentationContext { override fun onDispatched(result: CompletableFuture) { - executions.computeIfPresent(executionInput) { _, executionState -> + executions.computeIfPresent(executionId) { _, executionState -> executionState.fieldToDispatchedState(field, fieldExecutionStrategyPath, fieldGraphQLType, result) executionState } @@ -113,7 +139,7 @@ class SyncExecutionExhaustedState( } } override fun onCompleted(result: Any?, t: Throwable?) { - executions.computeIfPresent(executionInput) { _, executionState -> + executions.computeIfPresent(executionId) { _, executionState -> executionState.fieldToCompletedState(field, fieldExecutionStrategyPath, result) executionState } @@ -132,9 +158,12 @@ class SyncExecutionExhaustedState( * a scalar leaf or a [DataFetcher] that returns a [CompletableFuture] */ fun allSyncExecutionsExhausted(): Boolean = synchronized(executions) { + val operationsToExecute = totalExecutions.get() when { - executions.size < totalExecutions || !dataLoaderRegistry.onDispatchFuturesHandled() -> false - else -> executions.values.all(ExecutionBatchState::isSyncExecutionExhausted) + executions.size < operationsToExecute || !dataLoaderRegistry.onDispatchFuturesHandled() -> false + else -> { + executions.values.all(ExecutionBatchState::isSyncExecutionExhausted) + } } } } diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentationTest.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentationTest.kt index c919a41556..9fde9a5082 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentationTest.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/level/DataLoaderLevelDispatchedInstrumentationTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -159,6 +159,37 @@ class DataLoaderLevelDispatchedInstrumentationTest { } } + @Test + fun `Instrumentation should not account for invalid operations`() { + val queries = listOf( + "invalid query{ astronaut(id: 1) {", + "{ astronaut(id: 2) { id name } }", + "{ mission(id: 3) { id designation } }", + "{ mission(id: 4) { designation } }" + ) + + val (results, kotlinDataLoaderRegistry) = AstronautGraphQL.execute( + graphQL, + queries, + DataLoaderInstrumentationStrategy.LEVEL_DISPATCHED + ) + + assertEquals(4, results.size) + + val astronautStatistics = kotlinDataLoaderRegistry.dataLoadersMap["AstronautDataLoader"]?.statistics + val missionStatistics = kotlinDataLoaderRegistry.dataLoadersMap["MissionDataLoader"]?.statistics + + assertEquals(1, astronautStatistics?.batchInvokeCount) + assertEquals(1, astronautStatistics?.batchLoadCount) + + assertEquals(1, missionStatistics?.batchInvokeCount) + assertEquals(2, missionStatistics?.batchLoadCount) + + verify(exactly = 2 + ONE_LEVEL) { + kotlinDataLoaderRegistry.dispatchAll() + } + } + companion object { private const val ONE_LEVEL = 1 } diff --git a/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/DataLoaderSyncExecutionExhaustedInstrumentationTest.kt b/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/DataLoaderSyncExecutionExhaustedInstrumentationTest.kt index 79f8b87836..4147d49570 100644 --- a/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/DataLoaderSyncExecutionExhaustedInstrumentationTest.kt +++ b/executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/DataLoaderSyncExecutionExhaustedInstrumentationTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Expedia, Inc + * Copyright 2024 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -577,4 +577,35 @@ class DataLoaderSyncExecutionExhaustedInstrumentationTest { dataLoaderSyncExecutionExhaustedInstrumentation.dispatchAll() } } + + @Test + fun `Instrumentation should not account for invalid operations`() { + val queries = listOf( + "invalid query{ astronaut(id: 1) {", + "{ astronaut(id: 2) { id name } }", + "{ mission(id: 3) { id designation } }", + "{ mission(id: 4) { designation } }" + ) + + val (results, kotlinDataLoaderRegistry) = AstronautGraphQL.execute( + graphQL, + queries, + DataLoaderInstrumentationStrategy.SYNC_EXHAUSTION + ) + + assertEquals(4, results.size) + + val astronautStatistics = kotlinDataLoaderRegistry.dataLoadersMap["AstronautDataLoader"]?.statistics + val missionStatistics = kotlinDataLoaderRegistry.dataLoadersMap["MissionDataLoader"]?.statistics + + assertEquals(1, astronautStatistics?.batchInvokeCount) + assertEquals(1, astronautStatistics?.batchLoadCount) + + assertEquals(1, missionStatistics?.batchInvokeCount) + assertEquals(2, missionStatistics?.batchLoadCount) + + verify(exactly = 2) { + kotlinDataLoaderRegistry.dispatchAll() + } + } }