Skip to content
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

Resolve fields of objects lazily #1849

Merged
merged 4 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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