Skip to content
This repository has been archived by the owner on Oct 26, 2020. It is now read-only.

Faster version of the field selection merging validation #12

Merged
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
14 changes: 13 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import sbt.Keys.{crossScalaVersions, developers, organizationHomepage, scalacOpt
lazy val root = project
.in(file("."))
.withId("sangria-root")
.aggregate(core)
.aggregate(core, benchmarks)
.settings(
commonSettings,
noPublishSettings
Expand Down Expand Up @@ -66,6 +66,18 @@ lazy val core = project
"releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2")
)

lazy val benchmarks = project
.in(file("modules/benchmarks"))
.withId("sangria-benchmarks")
.dependsOn(core)
.enablePlugins(JmhPlugin)
.settings(
commonSettings,
noPublishSettings,
name := "sangria-benchmarks",
description := "Benchmarks of Sangria functionality",
)

/* Commonly used functionality across the projects
*/

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
package sangria.benchmarks

import org.openjdk.jmh.annotations._
import org.openjdk.jmh.infra.Blackhole
import sangria.ast.Document
import sangria.parser.QueryParser
import sangria.schema.Schema
import sangria.validation.rules
import sangria.validation.{QueryValidator, RuleBasedQueryValidator, Violation}

@State(Scope.Thread)
class OverlappingFieldsCanBeMergedBenchmark {

@Param(Array("Old", "New"))
var validatorType: String = _

var validator: QueryValidator = _

val schema: Schema[_, _] = {
Schema.buildFromAst(
QueryParser
.parse("""
type Query {
viewer: Viewer
}

interface Abstract {
field: Abstract
leaf: Int
}

interface Abstract1 {
field: Abstract
leaf: Int
}

interface Abstract2 {
field: Abstract
leaf: Int
}

type Concrete1 implements Abstract1

type Concrete2 implements Abstract2

type Viewer {
xingId: XingId
}
type XingId {
firstName: String!
lastName: String!
}
""")
.get
)
}


// @Param(Array("10", "20", "30", "50", "80", "110"))
@Param(Array("2", "10", "100"))
var size: Int = _

var overlapFrag: Document = _
var overlapNoFrag: Document = _
var noOverlapFrag: Document = _
var noOverlapNoFrag: Document = _
var repeatedFields: Document = _
var deepAbstractConcrete: Document = _

@Setup
def setup(): Unit = {
validator = validatorType match {
case "Old" => new RuleBasedQueryValidator(List(new rules.OverlappingFieldsCanBeMerged))
case "New" => new RuleBasedQueryValidator(List(new rules.experimental.OverlappingFieldsCanBeMerged))
}
overlapFrag = makeQuery(size, overlapping = true, fragments = true)
overlapNoFrag = makeQuery(size, overlapping = true, fragments = false)
noOverlapFrag = makeQuery(size, overlapping = false, fragments = true)
noOverlapNoFrag = makeQuery(size, overlapping = false, fragments = false)
repeatedFields = makeRepeatedFieldsQuery(size)
deepAbstractConcrete = makeDeepAbstractConcreteQuery(size)
}

@Benchmark
def benchmarkRepeatedFields(bh: Blackhole): Unit = {
bh.consume(doValidate(validator, repeatedFields))
}

@Benchmark
def benchmarkOverlapFrag(bh: Blackhole): Unit = {
bh.consume(doValidate(validator, overlapFrag))
}

@Benchmark
def benchmarkOverlapNoFrag(bh: Blackhole): Unit = {
bh.consume(doValidate(validator, overlapNoFrag))
}

@Benchmark
def benchmarkNoOverlapFrag(bh: Blackhole): Unit = {
bh.consume(doValidate(validator, noOverlapFrag))
}

@Benchmark
def benchmarkNoOverlapNoFrag(bh: Blackhole): Unit = {
bh.consume(doValidate(validator, noOverlapNoFrag))
}

@Benchmark
def benchmarkDeepAbstractConcrete(bh: Blackhole): Unit = {
bh.consume(doValidate(validator, deepAbstractConcrete))
}

private def doValidate(validator: QueryValidator, document: Document): Vector[Violation] = {
val result = validator.validateQuery(schema, document)
require(result.isEmpty)
result
}

private def makeQuery(size: Int, overlapping: Boolean, fragments: Boolean): Document = {
if (fragments) {
makeQueryWithFragments(size, overlapping)
} else {
makeQueryWithoutFragments(size, overlapping)
}
}

private def makeRepeatedFieldsQuery(size: Int): Document = {
val b = new StringBuilder

b.append("""
| query testQuery {
| viewer {
| xingId {
""".stripMargin)

for (i <- 1 to size) {
b.append("firstName\n")
}

b.append("""
| }
| }
|}
""".stripMargin)

QueryParser.parse(b.result()).get
}

private def makeQueryWithFragments(size: Int, overlapping: Boolean): Document = {
val b = new StringBuilder

for (i <- 1 to size) {
if (overlapping) {
b.append(s"""
|fragment mergeIdenticalFields${i} on Query {
| viewer {
| xingId {
| firstName
| lastName
| }
| }
|}
""".stripMargin)
} else {
b.append(s"""
|fragment mergeIdenticalFields${i} on Query {
| viewer${i} {
| xingId${i} {
| firstName${i}
| lastName${i}
| }
| }
|}
""".stripMargin)
}

b.append("\n\n")
}

b.append("query testQuery {")
for (i <- 1 to size) {
b.append(s"...mergeIdenticalFields${i}\n")
}
b.append("}")

QueryParser.parse(b.result()).get
}

private def makeQueryWithoutFragments(size: Int, overlapping: Boolean): Document = {
val b = new StringBuilder

b.append("query testQuery {")

for (i <- 1 to size) {
if (overlapping) {
b.append("""
|viewer {
| xingId {
| firstName
| }
|}
|
""".stripMargin)
} else {
b.append(s"""
| viewer${1} {
| xingId${i} {
| firstName${i}
| }
| }
""".stripMargin)
}

b.append("\n\n")
}

b.append("}")

QueryParser.parse(b.result()).get
}

private def makeDeepAbstractConcreteQuery(depth: Int): Document = {
val q = new StringBuilder

q.append(
"""
|fragment multiply on Whatever {
| field {
| ... on Abstract1 { field { leaf } }
| ... on Abstract2 { field { leaf } }
| ... on Concrete1 { field { leaf } }
| ... on Concrete2 { field { leaf } }
| }
|}
|
|query DeepAbstractConcrete {
|
|""".stripMargin)

(1 to depth).foreach { _ =>
q.append("field { ...multiply ")
}

(1 to depth).foreach { _ =>
q.append(" }")
}

q.append("\n}")

QueryParser.parse(q.result()).get
}
}
43 changes: 43 additions & 0 deletions modules/core/src/main/java/sangria/annotations/ApiMayChange.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package sangria.annotations;

/*
* Copied and adapted from the akka annotation @ApiMayChange
* The akka code was licensed under Apache License Version 2.0, January 2004 which is
* also used as the Sangria license and the following copyright notice was attached:
* Copyright (C) 2017-2019 Lightbend Inc. <https://www.lightbend.com>
*/

import java.lang.annotation.*;

/**
* Marks APIs that are meant to evolve towards becoming stable APIs, but are not stable APIs yet.
*
* <p>Evolving interfaces MAY change from one patch release to another (i.e. 2.4.10 to 2.4.11)
* without up-front notice. A best-effort approach is taken to not cause more breakage than really
* necessary, and usual deprecation techniques are utilised while evolving these APIs, however there
* is NO strong guarantee regarding the source or binary compatibility of APIs marked using this
* annotation.
*
* <p>It MAY also change when promoting the API to stable, for example such changes may include
* removal of deprecated methods that were introduced during the evolution and final refactoring
* that were deferred because they would have introduced to much breaking changes during the
* evolution phase.
*
* <p>Promoting the API to stable MAY happen in a patch release.
*
* <p>It is encouraged to document in ScalaDoc how exactly this API is expected to evolve.
*/
@Documented
@Retention(RetentionPolicy.CLASS) // to be accessible by MiMa (not currently used by Sangria)
@Target({
ElementType.METHOD,
ElementType.CONSTRUCTOR,
ElementType.FIELD,
ElementType.TYPE,
ElementType.PACKAGE
})
public @interface ApiMayChange {

/** Reference to issue discussing the future evolvement of this API */
String issue() default "";
}
Loading