Skip to content

Commit 85e9854

Browse files
committed
Merge branch 'master' into infer-nullity-3
# Conflicts: # src/main/java/graphql/schema/GraphQLSchema.java
2 parents b00dafe + 99dd2fd commit 85e9854

39 files changed

+723564
-143
lines changed

.github/workflows/master.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
- name: build and test
2424
run: ./gradlew ${{matrix.gradle-argument}} --info --stacktrace
2525
- name: Publish Test Results
26-
uses: EnricoMi/publish-unit-test-result-action@v2.21.0
26+
uses: EnricoMi/publish-unit-test-result-action@v2.22.0
2727
if: always()
2828
with:
2929
files: |

.github/workflows/pull_request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
- name: build and test
3333
run: ./gradlew ${{matrix.gradle-argument}} --info --stacktrace
3434
- name: Publish Test Results
35-
uses: EnricoMi/publish-unit-test-result-action@v2.21.0
35+
uses: EnricoMi/publish-unit-test-result-action@v2.22.0
3636
if: always()
3737
with:
3838
files: |

build.gradle

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ plugins {
1111
id 'maven-publish'
1212
id 'antlr'
1313
id 'signing'
14-
id "com.gradleup.shadow" version "9.0.0"
14+
id "com.gradleup.shadow" version "9.3.0"
1515
id "biz.aQute.bnd.builder" version "7.1.0"
1616
id "io.github.gradle-nexus.publish-plugin" version "2.0.0"
1717
id "groovy"
@@ -129,11 +129,11 @@ dependencies {
129129

130130
testImplementation 'org.junit.jupiter:junit-jupiter:5.14.1'
131131

132-
testImplementation 'org.spockframework:spock-core:2.3-groovy-4.0'
132+
testImplementation 'org.spockframework:spock-core:2.4-groovy-5.0'
133133
testImplementation 'net.bytebuddy:byte-buddy:1.18.1'
134-
testImplementation 'org.objenesis:objenesis:3.4'
135-
testImplementation 'org.apache.groovy:groovy:4.0.28"'
136-
testImplementation 'org.apache.groovy:groovy-json:4.0.28'
134+
testImplementation 'org.objenesis:objenesis:3.5'
135+
testImplementation 'org.apache.groovy:groovy:5.0.4'
136+
testImplementation 'org.apache.groovy:groovy-json:5.0.4'
137137
testImplementation 'com.google.code.gson:gson:2.13.2'
138138
testImplementation 'org.eclipse.jetty:jetty-server:11.0.26'
139139
testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.20.1'
@@ -223,6 +223,15 @@ tasks.named('jmhJar') {
223223
}
224224
}
225225

226+
jmh {
227+
if (project.hasProperty('jmhInclude')) {
228+
includes = [project.property('jmhInclude')]
229+
}
230+
if (project.hasProperty('jmhProfilers')) {
231+
profilers = [project.property('jmhProfilers')]
232+
}
233+
}
234+
226235

227236
task extractWithoutGuava(type: Copy) {
228237
from({ zipTree({ "build/libs/graphql-java-${project.version}.jar" }) }) {
@@ -361,6 +370,11 @@ tasks.register('testWithJava21', Test) {
361370
javaLauncher = javaToolchains.launcherFor {
362371
languageVersion = JavaLanguageVersion.of(21)
363372
}
373+
testClassesDirs = sourceSets.test.output.classesDirs
374+
classpath = sourceSets.test.runtimeClasspath
375+
classpath += sourceSets.jmh.output
376+
dependsOn "jmhClasses"
377+
dependsOn tasks.named('testClasses')
364378
}
365379

366380
tasks.register('testWithJava17', Test) {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package benchmark;
2+
3+
import graphql.schema.idl.FastSchemaGenerator;
4+
import graphql.schema.idl.RuntimeWiring;
5+
import graphql.schema.idl.SchemaGenerator;
6+
import graphql.schema.idl.SchemaParser;
7+
import graphql.schema.idl.TypeDefinitionRegistry;
8+
import org.openjdk.jmh.annotations.Benchmark;
9+
import org.openjdk.jmh.annotations.BenchmarkMode;
10+
import org.openjdk.jmh.annotations.Fork;
11+
import org.openjdk.jmh.annotations.Measurement;
12+
import org.openjdk.jmh.annotations.Mode;
13+
import org.openjdk.jmh.annotations.OutputTimeUnit;
14+
import org.openjdk.jmh.annotations.Scope;
15+
import org.openjdk.jmh.annotations.Setup;
16+
import org.openjdk.jmh.annotations.State;
17+
import org.openjdk.jmh.annotations.Warmup;
18+
import org.openjdk.jmh.infra.Blackhole;
19+
20+
import java.util.concurrent.TimeUnit;
21+
22+
@Warmup(iterations = 2, time = 5)
23+
@Measurement(iterations = 3)
24+
@Fork(2)
25+
@State(Scope.Benchmark)
26+
public class BuildSchemaBenchmark {
27+
28+
static String largeSDL = BenchmarkUtils.loadResource("large-schema-4.graphqls");
29+
30+
private TypeDefinitionRegistry registry;
31+
32+
@Setup
33+
public void setup() {
34+
// Parse SDL once before benchmarks run
35+
registry = new SchemaParser().parse(largeSDL);
36+
}
37+
38+
@Benchmark
39+
@BenchmarkMode(Mode.AverageTime)
40+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
41+
public void benchmarkBuildSchemaAvgTime(Blackhole blackhole) {
42+
blackhole.consume(new SchemaGenerator().makeExecutableSchema(registry, RuntimeWiring.MOCKED_WIRING));
43+
}
44+
45+
@Benchmark
46+
@BenchmarkMode(Mode.AverageTime)
47+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
48+
public void benchmarkBuildSchemaAvgTimeFast(Blackhole blackhole) {
49+
blackhole.consume(new FastSchemaGenerator().makeExecutableSchema(
50+
SchemaGenerator.Options.defaultOptions().withValidation(false),
51+
registry,
52+
RuntimeWiring.MOCKED_WIRING));
53+
}
54+
}

src/jmh/java/benchmark/CreateSchemaBenchmark.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package benchmark;
22

33
import graphql.schema.GraphQLSchema;
4+
import graphql.schema.idl.FastSchemaGenerator;
45
import graphql.schema.idl.RuntimeWiring;
56
import graphql.schema.idl.SchemaGenerator;
67
import graphql.schema.idl.SchemaParser;
@@ -21,7 +22,7 @@
2122
@Fork(2)
2223
public class CreateSchemaBenchmark {
2324

24-
static String largeSDL = BenchmarkUtils.loadResource("large-schema-3.graphqls");
25+
static String largeSDL = BenchmarkUtils.loadResource("large-schema-4.graphqls");
2526

2627
@Benchmark
2728
@BenchmarkMode(Mode.Throughput)
@@ -37,11 +38,26 @@ public void benchmarkLargeSchemaCreateAvgTime(Blackhole blackhole) {
3738
blackhole.consume(createSchema(largeSDL));
3839
}
3940

41+
@Benchmark
42+
@BenchmarkMode(Mode.AverageTime)
43+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
44+
public void benchmarkLargeSchemaCreateAvgTimeFast(Blackhole blackhole) {
45+
blackhole.consume(createSchemaFast(largeSDL));
46+
}
47+
4048
private static GraphQLSchema createSchema(String sdl) {
4149
TypeDefinitionRegistry registry = new SchemaParser().parse(sdl);
4250
return new SchemaGenerator().makeExecutableSchema(registry, RuntimeWiring.MOCKED_WIRING);
4351
}
4452

53+
private static GraphQLSchema createSchemaFast(String sdl) {
54+
TypeDefinitionRegistry registry = new SchemaParser().parse(sdl);
55+
return new FastSchemaGenerator().makeExecutableSchema(
56+
SchemaGenerator.Options.defaultOptions().withValidation(false),
57+
registry,
58+
RuntimeWiring.MOCKED_WIRING);
59+
}
60+
4561
@SuppressWarnings("InfiniteLoopStatement")
4662
/// make this a main method if you want to run it in JProfiler etc..
4763
public static void mainXXX(String[] args) {
@@ -54,4 +70,4 @@ public static void mainXXX(String[] args) {
5470
}
5571
}
5672
}
57-
}
73+
}

src/main/java/graphql/Assert.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import java.util.Collection;
77
import java.util.function.Supplier;
8-
import java.util.regex.Pattern;
98

109
import static java.lang.String.format;
1110

@@ -224,8 +223,6 @@ public static void assertFalse(boolean condition, String msgFmt, Object arg1, Ob
224223

225224
private static final String invalidNameErrorMessage = "Name must be non-null, non-empty and match [_A-Za-z][_0-9A-Za-z]* - was '%s'";
226225

227-
private static final Pattern validNamePattern = Pattern.compile("[_A-Za-z][_0-9A-Za-z]*");
228-
229226
/**
230227
* Validates that the Lexical token name matches the current spec.
231228
* currently non null, non empty,
@@ -234,11 +231,37 @@ public static void assertFalse(boolean condition, String msgFmt, Object arg1, Ob
234231
*
235232
* @return the name if valid, or AssertException if invalid.
236233
*/
237-
public static String assertValidName(String name) {
238-
if (name != null && !name.isEmpty() && validNamePattern.matcher(name).matches()) {
234+
public static String assertValidName(@Nullable String name) {
235+
if (name != null && isValidName(name)) {
239236
return name;
240237
}
241-
return throwAssert(invalidNameErrorMessage, name);
238+
return throwAssert(invalidNameErrorMessage, String.valueOf(name));
239+
}
240+
241+
/**
242+
* Fast character-by-character validation without regex.
243+
* Checks if name matches [_A-Za-z][_0-9A-Za-z]*
244+
*/
245+
private static boolean isValidName(String name) {
246+
if (name.isEmpty()) {
247+
return false;
248+
}
249+
250+
// First character must be [_A-Za-z]
251+
char first = name.charAt(0);
252+
if (!(first == '_' || (first >= 'A' && first <= 'Z') || (first >= 'a' && first <= 'z'))) {
253+
return false;
254+
}
255+
256+
// Remaining characters must be [_0-9A-Za-z]
257+
for (int i = 1; i < name.length(); i++) {
258+
char c = name.charAt(i);
259+
if (!(c == '_' || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) {
260+
return false;
261+
}
262+
}
263+
264+
return true;
242265
}
243266

244267
private static <T> T throwAssert(String format, Object... args) {

src/main/java/graphql/Directives.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
import graphql.schema.GraphQLDirective;
99
import org.jspecify.annotations.NullMarked;
1010

11+
import java.util.Collections;
12+
import java.util.LinkedHashMap;
13+
import java.util.LinkedHashSet;
14+
import java.util.Map;
15+
import java.util.Set;
1116
import java.util.concurrent.atomic.AtomicBoolean;
1217

1318
import static graphql.Scalars.GraphQLBoolean;
@@ -251,6 +256,57 @@ public class Directives {
251256
.definition(EXPERIMENTAL_DISABLE_ERROR_PROPAGATION_DIRECTIVE_DEFINITION)
252257
.build();
253258

259+
/**
260+
* The set of all built-in directives that are always present in a graphql schema.
261+
* The iteration order is stable and meaningful.
262+
*/
263+
public static final Set<GraphQLDirective> BUILT_IN_DIRECTIVES;
264+
265+
/**
266+
* A map from directive name to directive for all built-in directives.
267+
*/
268+
public static final Map<String, GraphQLDirective> BUILT_IN_DIRECTIVES_MAP;
269+
270+
static {
271+
LinkedHashSet<GraphQLDirective> directives = new LinkedHashSet<>();
272+
directives.add(IncludeDirective);
273+
directives.add(SkipDirective);
274+
directives.add(DeprecatedDirective);
275+
directives.add(SpecifiedByDirective);
276+
directives.add(OneOfDirective);
277+
directives.add(DeferDirective);
278+
directives.add(ExperimentalDisableErrorPropagationDirective);
279+
BUILT_IN_DIRECTIVES = Collections.unmodifiableSet(directives);
280+
281+
LinkedHashMap<String, GraphQLDirective> map = new LinkedHashMap<>();
282+
for (GraphQLDirective d : BUILT_IN_DIRECTIVES) {
283+
map.put(d.getName(), d);
284+
}
285+
BUILT_IN_DIRECTIVES_MAP = Collections.unmodifiableMap(map);
286+
}
287+
288+
/**
289+
* Returns true if a directive with the provided name is a built-in directive.
290+
*
291+
* @param directiveName the name of the directive in question
292+
*
293+
* @return true if the directive is built-in, false otherwise
294+
*/
295+
public static boolean isBuiltInDirective(String directiveName) {
296+
return BUILT_IN_DIRECTIVES_MAP.containsKey(directiveName);
297+
}
298+
299+
/**
300+
* Returns true if the provided directive is a built-in directive.
301+
*
302+
* @param directive the directive in question
303+
*
304+
* @return true if the directive is built-in, false otherwise
305+
*/
306+
public static boolean isBuiltInDirective(GraphQLDirective directive) {
307+
return isBuiltInDirective(directive.getName());
308+
}
309+
254310
private static Description createDescription(String s) {
255311
return new Description(s, null, false);
256312
}

src/main/java/graphql/introspection/IntrospectionResultToSchema.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
import static graphql.Assert.assertShouldNeverHappen;
4242
import static graphql.Assert.assertTrue;
4343
import static graphql.collect.ImmutableKit.map;
44-
import static graphql.schema.idl.DirectiveInfo.isGraphqlSpecifiedDirective;
44+
import static graphql.Directives.isBuiltInDirective;
4545

4646
@SuppressWarnings("unchecked")
4747
@PublicApi
@@ -131,7 +131,7 @@ public Document createSchemaDefinition(Map<String, Object> introspectionResult)
131131

132132
private DirectiveDefinition createDirective(Map<String, Object> input) {
133133
String directiveName = (String) input.get("name");
134-
if (isGraphqlSpecifiedDirective(directiveName)) {
134+
if (isBuiltInDirective(directiveName)) {
135135
return null;
136136
}
137137

src/main/java/graphql/normalized/ExecutableNormalizedField.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,13 @@ public void forEachFieldDefinition(GraphQLSchema schema, Consumer<GraphQLFieldDe
198198
return;
199199
}
200200

201+
var fieldVisibility = schema.getCodeRegistry().getFieldVisibility();
201202
for (String objectTypeName : objectTypeNames) {
202203
GraphQLObjectType type = (GraphQLObjectType) assertNotNull(schema.getType(objectTypeName));
203-
consumer.accept(assertNotNull(type.getField(fieldName), "No field %s found for type %s", fieldName, objectTypeName));
204+
// Use field visibility to allow custom visibility implementations to provide placeholder fields
205+
// for fields that don't exist on the local schema (e.g., in federated subgraphs)
206+
GraphQLFieldDefinition field = fieldVisibility.getFieldDefinition(type, fieldName);
207+
consumer.accept(assertNotNull(field, "No field %s found for type %s", fieldName, objectTypeName));
204208
}
205209
}
206210

@@ -223,7 +227,8 @@ private GraphQLFieldDefinition getOneFieldDefinition(GraphQLSchema schema) {
223227

224228
String objectTypeName = objectTypeNames.iterator().next();
225229
GraphQLObjectType type = (GraphQLObjectType) assertNotNull(schema.getType(objectTypeName));
226-
return assertNotNull(type.getField(fieldName), "No field %s found for type %s", fieldName, objectTypeName);
230+
var fieldVisibility = schema.getCodeRegistry().getFieldVisibility();
231+
return assertNotNull(fieldVisibility.getFieldDefinition(type, fieldName), "No field %s found for type %s", fieldName, objectTypeName);
227232
}
228233

229234
private static GraphQLFieldDefinition resolveIntrospectionField(GraphQLSchema schema, Set<String> objectTypeNames, String fieldName) {

0 commit comments

Comments
 (0)