Skip to content

Commit

Permalink
More object field resolution / reduction optimizations (#2029)
Browse files Browse the repository at this point in the history
* Use a mutable map in ObjectFieldResolver and reduce branches in object step reduction

* Fix test

* Fix Scala 2.12 compiling
  • Loading branch information
kyri-petrou authored Dec 7, 2023
1 parent 52409f6 commit ccb9716
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 29 deletions.
6 changes: 5 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import com.typesafe.tools.mima.core.{ IncompatibleMethTypeProblem, IncompatibleResultTypeProblem, ProblemFilters }
import org.scalajs.linker.interface.ModuleSplitStyle
import sbtcrossproject.CrossPlugin.autoImport.{ crossProject, CrossType }

Expand Down Expand Up @@ -671,7 +672,10 @@ lazy val enableMimaSettingsJVM =
Def.settings(
mimaFailOnProblem := enforceMimaCompatibility,
mimaPreviousArtifacts := previousStableVersion.value.map(organization.value %% moduleName.value % _).toSet,
mimaBinaryIssueFilters ++= Seq()
mimaBinaryIssueFilters ++= Seq(
ProblemFilters.exclude[IncompatibleMethTypeProblem]("caliban.schema.Step#ObjectStep*"),
ProblemFilters.exclude[IncompatibleResultTypeProblem]("caliban.schema.Step#ObjectStep*")
)
)

lazy val enableMimaSettingsJS =
Expand Down
30 changes: 16 additions & 14 deletions core/src/main/scala/caliban/execution/Executor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ object Executor {
queryExecution: QueryExecution = QueryExecution.Parallel,
featureSet: Set[Feature] = Set.empty
)(implicit trace: Trace): URIO[R, GraphQLResponse[CalibanError]] = {
val wrapPureValues = fieldWrappers.exists(_.wrapPureValues)
val wrapPureValues = fieldWrappers.exists(_.wrapPureValues)
val isDeferredEnabled = featureSet(Feature.Defer)

type ExecutionQuery[+A] = ZQuery[R, ExecutionError, A]

val execution = request.operationType match {
Expand All @@ -57,32 +59,32 @@ object Executor {
path: List[Either[String, Int]]
): ReducedStep[R] = {

def reduceObjectStep(objectName: String, fields: Map[String, Step[R]]) = {
def reduceObjectStep(objectName: String, fields: collection.Map[String, Step[R]]): ReducedStep[R] = {
val filteredFields = mergeFields(currentField, objectName)
val (deferred, eager) = filteredFields.partitionMap {
case f @ Field("__typename", _, _, _, _, _, _, directives, _, _, _) =>
Right((f.aliasedName, PureStep(StringValue(objectName)), fieldInfo(f, path, directives)))
case f @ Field(name, _, _, _, _, _, args, directives, _, _, fragment) =>
val aliasedName = f.aliasedName
val field = fields
.get(name)
.fold(NullStep: ReducedStep[R])(reduceStep(_, f, args, Left(aliasedName) :: path))

val info = fieldInfo(f, path, directives)
val field = fields.get(name) match {
case Some(step) => reduceStep(step, f, args, Left(f.aliasedName) :: path)
case _ => NullStep
}
val entry = (f.aliasedName, field, fieldInfo(f, path, directives))

fragment.collectFirst {
fragment match {
// The defer spec provides some latitude on how we handle responses. Since it is more performant to return
// pure fields rather than spin up the defer machinery we return pure fields immediately to the caller.
case IsDeferred(label) if featureSet(Feature.Defer) && !field.isPure =>
(label, (aliasedName, field, info))
}.toLeft((aliasedName, field, info))
case Some(IsDeferred(label)) if isDeferredEnabled && !field.isPure => Left((label, entry))
case _ => Right(entry)
}
}

val eagerReduced = reduceObject(eager, wrapPureValues)
deferred match {
case Nil => reduceObject(eager, wrapPureValues)
case Nil => eagerReduced
case d =>
DeferStep(
reduceObject(eager, wrapPureValues),
eagerReduced,
d.groupBy(_._1).toList.map { case (label, labelAndFields) =>
val (_, fields) = labelAndFields.unzip
reduceObject(fields, wrapPureValues) -> label
Expand Down
12 changes: 6 additions & 6 deletions core/src/main/scala/caliban/schema/ObjectFieldResolver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package caliban.schema
import caliban.execution.Field
import caliban.schema.Step.{ MetadataFunctionStep, ObjectStep }

import scala.collection.immutable.HashMap
import scala.collection.mutable

final private class ObjectFieldResolver[R, A](
objectName: String,
Expand All @@ -22,15 +22,15 @@ final private class ObjectFieldResolver[R, A](
value: A,
field: Field
): Step[R] = {
val fieldsBuilder = HashMap.newBuilder[String, Step[R]]
var remaining = field.distinctFieldNames
val fieldsBuilder = new mutable.HashMap[String, Step[R]]()

var remaining = field.distinctFieldNames
while (!remaining.isEmpty) {
val name = remaining.head
val resolve = fieldsMap.get(name)
if (resolve eq null) ()
else fieldsBuilder += name -> resolve(value)
if (resolve ne null) fieldsBuilder.update(name, resolve(value))
remaining = remaining.tail
}
ObjectStep(objectName, fieldsBuilder.result())
ObjectStep(objectName, fieldsBuilder)
}
}
14 changes: 7 additions & 7 deletions core/src/main/scala/caliban/schema/Step.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ import caliban.CalibanError.ExecutionError
import caliban.Value.NullValue
import caliban.execution.{ Field, FieldInfo }
import caliban.{ InputValue, ResponseValue }
import zio.stream.ZStream
import zio.query.ZQuery
import zio.stream.ZStream

sealed trait Step[-R]

object Step {
case class ListStep[-R](steps: List[Step[R]]) extends Step[R]
case class FunctionStep[-R](step: Map[String, InputValue] => Step[R]) extends Step[R]
case class MetadataFunctionStep[-R](step: Field => Step[R]) extends Step[R]
case class ObjectStep[-R](name: String, fields: Map[String, Step[R]]) extends Step[R]
case class QueryStep[-R](query: ZQuery[R, Throwable, Step[R]]) extends Step[R]
case class StreamStep[-R](inner: ZStream[R, Throwable, Step[R]]) extends Step[R]
case class ListStep[-R](steps: List[Step[R]]) extends Step[R]
case class FunctionStep[-R](step: Map[String, InputValue] => Step[R]) extends Step[R]
case class MetadataFunctionStep[-R](step: Field => Step[R]) extends Step[R]
case class ObjectStep[-R](name: String, fields: collection.Map[String, Step[R]]) extends Step[R]
case class QueryStep[-R](query: ZQuery[R, Throwable, Step[R]]) extends Step[R]
case class StreamStep[-R](inner: ZStream[R, Throwable, Step[R]]) extends Step[R]

// PureStep is both a Step and a ReducedStep so it is defined outside this object
// This is to avoid boxing/unboxing pure values during step reduction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ object Fs2InteropSchemaSpec extends ZIOSpecDefault {
isSubtype[ObjectStep[Any]](
hasField(
"fields",
_.fields,
_.fields.toMap,
hasKey(
"bar",
isSubtype[StreamStep[Any]](
Expand Down

0 comments on commit ccb9716

Please sign in to comment.