Skip to content

Commit

Permalink
Resolve fields of objects lazily (#1849)
Browse files Browse the repository at this point in the history
* Resolve object fields lazily

* Maybe resolve Map schemas lazily

* Fix Scala 3

* Cleanups
  • Loading branch information
kyri-petrou authored Aug 25, 2023
1 parent 7896591 commit 40adc9d
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 9 deletions.
10 changes: 9 additions & 1 deletion core/src/main/scala-2/caliban/schema/SchemaDerivation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,22 @@ trait CommonSchemaDerivation[R] {
Some(ctx.typeName.full)
)

override private[schema] def resolveFieldLazily: Boolean = !ctx.isObject

override def resolve(value: T): Step[R] =
if (ctx.isObject) PureStep(EnumValue(getName(ctx)))
else if ((ctx.isValueClass || isValueType(ctx)) && ctx.parameters.nonEmpty) {
val head = ctx.parameters.head
head.typeclass.resolve(head.dereference(value))
} else {
val fields = Map.newBuilder[String, Step[R]]
ctx.parameters.foreach(p => fields += getName(p) -> p.typeclass.resolve(p.dereference(value)))
ctx.parameters.foreach(p =>
fields += getName(p) -> {
lazy val step = p.typeclass.resolve(p.dereference(value))
if (p.typeclass.resolveFieldLazily) FunctionStep(_ => step)
else step
}
)
ObjectStep(getName(ctx), fields.result())
}
}
Expand Down
12 changes: 8 additions & 4 deletions core/src/main/scala-3/caliban/schema/SchemaDerivation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import caliban.Value.EnumValue
import caliban.introspection.adt.*
import caliban.parsing.adt.Directive
import caliban.schema.Annotations.*
import caliban.schema.Step.ObjectStep
import caliban.schema.Step.{ FunctionStep, ObjectStep }
import caliban.schema.Types.*
import caliban.schema.macros.{ Macros, TypeInfo }

Expand Down Expand Up @@ -154,6 +154,8 @@ trait CommonSchemaDerivation {
else if (isInput) mkInputObject[R](annotations, fields, info, paramAnnotations)(isInput, isSubscription)
else mkObject[R](annotations, fields, info, paramAnnotations)(isInput, isSubscription)

override private[schema] lazy val resolveFieldLazily: Boolean = fields.nonEmpty

def resolve(value: A): Step[R] =
if (fields.isEmpty) PureStep(EnumValue(name))
else if (isValueType) {
Expand All @@ -163,9 +165,11 @@ trait CommonSchemaDerivation {
val fieldsBuilder = Map.newBuilder[String, Step[R]]
fields.foreach { case (label, _, schema, index) =>
val fieldAnnotations = paramAnnotations.getOrElse(label, Nil)
fieldsBuilder += getName(fieldAnnotations, label) -> schema.resolve(
value.asInstanceOf[Product].productElement(index)
)
lazy val step = schema.resolve(value.asInstanceOf[Product].productElement(index))
fieldsBuilder += getName(fieldAnnotations, label) -> {
if (schema.resolveFieldLazily) FunctionStep(_ => step)
else step
}
}
ObjectStep(name, fieldsBuilder.result())
}
Expand Down
23 changes: 19 additions & 4 deletions core/src/main/scala/caliban/schema/Schema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ trait Schema[-R, T] { self =>
*/
def arguments: List[__InputValue] = Nil

/**
* Boolean flag indicating whether the [[resolve]] method should be wrapped in a [[FunctionStep]] when resolving
* object fields.
*
* Should be `false` except for objects or wrappers of objects. This prevents fields of case classes to be resolved when they're not
* requested in the query, which significantly improves performance in larger schemas
*/
private[schema] def resolveFieldLazily: Boolean = false

/**
* Builds a new `Schema` of `A` from an existing `Schema` of `T` and a function from `A` to `T`.
* @param f a function from `A` to `T`.
Expand All @@ -90,6 +99,7 @@ trait Schema[-R, T] { self =>
override def arguments: List[__InputValue] = self.arguments
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = self.toType_(isInput, isSubscription)
override def resolve(value: A): Step[R] = self.resolve(f(value))
override private[schema] def resolveFieldLazily: Boolean = self.resolveFieldLazily
}

/**
Expand All @@ -110,7 +120,8 @@ trait Schema[-R, T] { self =>
case _ => true
}

override def resolve(value: T): Step[R] =
override private[schema] def resolveFieldLazily: Boolean = self.resolveFieldLazily
override def resolve(value: T): Step[R] =
self.resolve(value) match {
case o @ ObjectStep(_, fields) =>
if (renameTypename) ObjectStep(name, fields) else o
Expand Down Expand Up @@ -174,7 +185,8 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {

private lazy val fieldsForResolve = fields(false, false)

override def resolve(value: A): Step[R1] = {
override private[schema] def resolveFieldLazily: Boolean = true
override def resolve(value: A): Step[R1] = {
val fieldsBuilder = Map.newBuilder[String, Step[R1]]
fieldsForResolve.foreach { case (f, plan) => fieldsBuilder += f.name -> plan(value) }
ObjectStep(name, fieldsBuilder.result())
Expand Down Expand Up @@ -282,7 +294,8 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
override def optional: Boolean = true
override def toType(isInput: Boolean, isSubscription: Boolean): __Type = ev.toType_(isInput, isSubscription)

override def resolve(value: Option[A]): Step[R0] =
override private[schema] def resolveFieldLazily: Boolean = ev.resolveFieldLazily
override def resolve(value: Option[A]): Step[R0] =
value match {
case Some(value) => ev.resolve(value)
case None => NullStep
Expand All @@ -294,7 +307,8 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
(if (ev.optional) t else t.nonNull).list
}

override def resolve(value: List[A]): Step[R0] = ListStep(value.map(ev.resolve))
override private[schema] def resolveFieldLazily: Boolean = ev.resolveFieldLazily
override def resolve(value: List[A]): Step[R0] = ListStep(value.map(ev.resolve))
}
implicit def setSchema[R0, A](implicit ev: Schema[R0, A]): Schema[R0, Set[A]] = listSchema[R0, A].contramap(_.toList)
implicit def seqSchema[R0, A](implicit ev: Schema[R0, A]): Schema[R0, Seq[A]] = listSchema[R0, A].contramap(_.toList)
Expand Down Expand Up @@ -379,6 +393,7 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema {
override def toType(isInput: Boolean, isSubscription: Boolean): __Type =
kvSchema.toType_(isInput, isSubscription).nonNull.list

override private[schema] def resolveFieldLazily: Boolean = evA.resolveFieldLazily || evB.resolveFieldLazily
override def resolve(value: Map[A, B]): Step[RA with RB] = ListStep(value.toList.map(kvSchema.resolve))
}
implicit def functionSchema[RA, RB, A, B](implicit
Expand Down

0 comments on commit 40adc9d

Please sign in to comment.