Skip to content

Commit

Permalink
Improved execution field merging (#2019)
Browse files Browse the repository at this point in the history
* Avoid unnecessary field merging during execution

* Cleanup

* PR comments

* Extract Map size calculation into separate method
  • Loading branch information
kyri-petrou authored Nov 25, 2023
1 parent 05858ab commit 3388667
Showing 1 changed file with 47 additions and 18 deletions.
65 changes: 47 additions & 18 deletions core/src/main/scala/caliban/execution/Executor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -306,27 +306,43 @@ object Executor {
ZIO.succeed(GraphQLResponse(NullValue, List(error)))

private[caliban] def mergeFields(field: Field, typeName: String): List[Field] = {
val map = new java.util.LinkedHashMap[String, Field]()
var modified = false

field.fields.foreach { field =>
if (field._condition.forall(_.contains(typeName))) {
map.compute(
field.aliasedName,
(_, f) =>
if (f == null) field
else {
modified = true
f.copy(fields = f.fields ::: field.fields)
}
)
} else {
modified = true
def haveSameCondition(head: Field, tail: List[Field]): Boolean = {
val condition = head._condition
var remaining = tail
while (!remaining.isEmpty) {
if (remaining.head._condition != condition) return false
remaining = remaining.tail
}
true
}

def matchesTypename(f: Field): Boolean =
f._condition.isEmpty || f._condition.get.contains(typeName)

def mergeFields(fields: List[Field]) = {
val map = new java.util.LinkedHashMap[String, Field](calculateMapCapacity(fields.size))
var remaining = fields
while (!remaining.isEmpty) {
val h = remaining.head
if (matchesTypename(h)) {
map.compute(
h.aliasedName,
(_, f) =>
if (f eq null) h
else f.copy(fields = f.fields ::: h.fields)
)
}
remaining = remaining.tail
}
map.values().asScala.toList
}

// Avoid conversions if no modification took place
if (modified) map.values().asScala.toList else field.fields
field.fields match {
// Shortcut if all the fields have the same condition, which means we don't need to merge as that's been handled in Field.apply
case h :: t if haveSameCondition(h, t) => if (matchesTypename(h)) field.fields else Nil
case Nil => Nil
case fields => mergeFields(fields)
}
}

private def fieldInfo(field: Field, path: List[Either[String, Int]], fieldDirectives: List[Directive]): FieldInfo =
Expand Down Expand Up @@ -370,4 +386,17 @@ object Executor {
(l.result(), r.result())
}
}

/**
* The behaviour of mutable Maps (both Java and Scala) is to resize once the number of entries exceeds
* the capacity * loadFactor (default of 0.75d) threshold in order to prevent hash collisions.
*
* This method is a helper method to estimate the initial map size depending on the number of elements the Map is
* expected to hold
*
* NOTE: This method is the same as java.util.HashMap.calculateHashMapCapacity on JDK19+
*/
private def calculateMapCapacity(nMappings: Int): Int =
Math.ceil(nMappings / 0.75d).toInt

}

0 comments on commit 3388667

Please sign in to comment.