Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e105a49
Optimize assertValidName by replacing regex with character-by-charact…
Dec 11, 2025
cadb12a
Phase 1: Add FastBuilder scaffolding with scalar type support
Dec 6, 2025
0f81a9c
Phase 2: Add directive support with type reference resolution
Dec 6, 2025
e39bf20
Phase 3: Add enumeration type support
Dec 6, 2025
e473ca8
Phase 4: Add input object type support with type reference resolution
Dec 6, 2025
d09a763
Phase 5: Add applied directive support with type reference resolution
Dec 6, 2025
798c7e1
Phase 6: Object Types - FastBuilder implementation
Dec 6, 2025
ea5d3cc
Phase 7: Interface Types - FastBuilder implementation
Dec 6, 2025
4472f7c
Phase 8: Union Types - FastBuilder implementation
Dec 6, 2025
e12c074
Phase 9: Validation and Edge Cases - FastBuilder implementation
Dec 6, 2025
b7e9e46
Add FastSchemaGenerator and JMH benchmark infrastructure
Dec 20, 2025
388ab73
Address review comment, change a name
Jan 11, 2026
62815b1
Tighter type-bounds for GraphQLSchema.additionalTypes.
Jan 20, 2026
1c54eae
Remove detached-type detection: all types are additionalTypes
Jan 20, 2026
46e2e9b
Merge remote-tracking branch 'origin/master' into fastbuilder
andimarek Jan 22, 2026
7cf12cd
Remove phase separator comments from FastBuilder tests
andimarek Jan 22, 2026
c446304
Fix FastBuilder Javadoc comments
andimarek Jan 22, 2026
16a8fc2
Change FastBuilder.addType to accept GraphQLNamedType
andimarek Jan 22, 2026
d430e66
Fixes
Jan 23, 2026
a15baf6
Refactor built-in directive handling
Jan 25, 2026
eb8e24c
Fix withSchemaAppliedDirectives(Collection) not scanning for type ref…
andimarek Jan 28, 2026
86da667
Merge remote-tracking branch 'upstream/master' into fastbuilder
Jan 29, 2026
719f91f
Remove cruft
Jan 29, 2026
14a31e4
Improve FastBuilder documentation and add @NullMarked
andimarek Jan 29, 2026
cd32b5c
Remove redundant checks in assertValidName, mark param @Nullable
andimarek Jan 29, 2026
131f169
Promote FastSchemaGenerator to @ExperimentalApi with safer defaults
Jan 30, 2026
23c59ae
Add type resolver validation to FastBuilder
Jan 30, 2026
5bcba4a
Merge remote-tracking branch 'upstream/master' into fastbuilder
Jan 30, 2026
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
Prev Previous commit
Next Next commit
Fixes
* Add all introspective types
* Add scalar types needed by introspective types
* Add regression tests that missed the above
* Remove tests invalidated by recent code changes
  • Loading branch information
Raymie Stata
Raymie Stata committed Jan 23, 2026
commit d430e66c0c0a5a6fa98aba7a25e864efe7652f76
17 changes: 17 additions & 0 deletions src/main/java/graphql/schema/GraphQLSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import graphql.Assert;
import graphql.AssertException;
import graphql.Directives;
import graphql.Scalars;
import graphql.DirectivesUtil;
import graphql.ExperimentalApi;
import graphql.Internal;
Expand Down Expand Up @@ -1135,6 +1136,22 @@ public FastBuilder(GraphQLCodeRegistry.Builder codeRegistryBuilder,
// Add introspection code to the registry
Introspection.addCodeForIntrospectionTypes(codeRegistryBuilder);

// Add introspection types to the type map
// These must be present for introspection queries to work correctly
addType(Introspection.__Schema);
addType(Introspection.__Type);
addType(Introspection.__Field);
addType(Introspection.__InputValue);
addType(Introspection.__EnumValue);
addType(Introspection.__Directive);
addType(Introspection.__TypeKind);
addType(Introspection.__DirectiveLocation);

// Add String and Boolean scalars required by introspection types
// (e.g., __Type.name returns String, __Field.isDeprecated returns Boolean)
addType(Scalars.GraphQLString);
addType(Scalars.GraphQLBoolean);

// Add root types
addType(queryType);
if (mutationType != null) {
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/graphql/schema/idl/FastSchemaGenerator.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package graphql.schema.idl;

import graphql.GraphQLError;
import graphql.Internal;
import graphql.language.OperationTypeDefinition;
import graphql.schema.GraphQLCodeRegistry;
import graphql.schema.GraphQLDirective;
import graphql.schema.GraphQLNamedType;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.errors.SchemaProblem;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
Expand All @@ -20,6 +23,7 @@
@Internal
public class FastSchemaGenerator {

private final SchemaTypeChecker typeChecker = new SchemaTypeChecker();
private final SchemaGeneratorHelper schemaGeneratorHelper = new SchemaGeneratorHelper();

/**
Expand Down Expand Up @@ -51,6 +55,12 @@ public GraphQLSchema makeExecutableSchema(SchemaGenerator.Options options, TypeD
// Use immutable registry for faster operations
ImmutableTypeDefinitionRegistry fasterImmutableRegistry = typeRegistryCopy.readOnly();

// Check type registry for errors
List<GraphQLError> errors = typeChecker.checkTypeRegistry(fasterImmutableRegistry, wiring);
if (!errors.isEmpty()) {
throw new SchemaProblem(errors);
}

Map<String, OperationTypeDefinition> operationTypeDefinitions = SchemaExtensionsChecker.gatherOperationDefs(fasterImmutableRegistry);

return makeExecutableSchemaImpl(fasterImmutableRegistry, wiring, operationTypeDefinitions, options);
Expand Down
48 changes: 0 additions & 48 deletions src/test/groovy/graphql/schema/FastBuilderTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -80,27 +80,6 @@ class FastBuilderTest extends Specification {
schema.getType("MyScalar") != null
}

def "null type is safely ignored"() {
given: "a query type"
def queryType = newObject()
.name("Query")
.field(newFieldDefinition()
.name("value")
.type(GraphQLString))
.build()

and: "code registry"
def codeRegistry = GraphQLCodeRegistry.newCodeRegistry()

when: "adding null type"
def schema = new GraphQLSchema.FastBuilder(codeRegistry, queryType, null, null)
.addType(null)
.build()

then: "no error"
schema.queryType.name == "Query"
}

def "query type is required"() {
when: "creating FastBuilder with null query type"
new GraphQLSchema.FastBuilder(GraphQLCodeRegistry.newCodeRegistry(), null, null, null)
Expand Down Expand Up @@ -2002,31 +1981,4 @@ class FastBuilderTest extends Specification {
def searchField = schema.queryType.getFieldDefinition("search")
searchField.getArgument("filter").getType() == filterInput
}

def "null types and directives are ignored"() {
given: "a query type"
def queryType = newObject()
.name("Query")
.field(newFieldDefinition()
.name("value")
.type(GraphQLString))
.build()

when: "adding null types and directives"
def schema = new GraphQLSchema.FastBuilder(
GraphQLCodeRegistry.newCodeRegistry(), queryType, null, null)
.addType(null)
.addTypes(null)
.additionalDirective(null)
.additionalDirectives(null)
.withSchemaDirective(null)
.withSchemaDirectives(null)
.withSchemaAppliedDirective(null)
.withSchemaAppliedDirectives(null)
.build()

then: "no error and schema builds"
schema != null
schema.queryType.name == "Query"
}
}
143 changes: 143 additions & 0 deletions src/test/groovy/graphql/schema/idl/FastSchemaGeneratorTest.groovy
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package graphql.schema.idl

import graphql.schema.GraphQLSchema
import graphql.schema.idl.errors.SchemaProblem
import spock.lang.Specification

class FastSchemaGeneratorTest extends Specification {
Expand Down Expand Up @@ -134,4 +135,146 @@ class FastSchemaGeneratorTest extends Specification {
schema.getType("User") != null
schema.getType("Post") != null
}

// Regression tests to ensure FastSchemaGenerator behaves like SchemaGenerator

def "should throw SchemaProblem for missing type reference"() {
given:
def sdl = '''
type Query {
user: UnknownType
}
'''

when:
new FastSchemaGenerator().makeExecutableSchema(
new SchemaParser().parse(sdl),
RuntimeWiring.MOCKED_WIRING
)

then:
thrown(SchemaProblem)
}

def "should throw SchemaProblem for duplicate field definitions"() {
given:
def sdl = '''
type Query {
hello: String
hello: Int
}
'''

when:
new FastSchemaGenerator().makeExecutableSchema(
new SchemaParser().parse(sdl),
RuntimeWiring.MOCKED_WIRING
)

then:
thrown(SchemaProblem)
}

def "should throw SchemaProblem for invalid interface implementation"() {
given:
def sdl = '''
type Query {
node: Node
}

interface Node {
id: ID!
}

type User implements Node {
name: String
}
'''

when:
new FastSchemaGenerator().makeExecutableSchema(
new SchemaParser().parse(sdl),
RuntimeWiring.MOCKED_WIRING
)

then:
// User claims to implement Node but doesn't have the required 'id' field
thrown(SchemaProblem)
}

def "should include introspection types in getAllTypesAsList"() {
given:
def sdl = '''
type Query {
hello: String
}
'''

when:
def schema = new FastSchemaGenerator().makeExecutableSchema(
new SchemaParser().parse(sdl),
RuntimeWiring.MOCKED_WIRING
)
def allTypeNames = schema.getAllTypesAsList().collect { it.name } as Set

then:
// Introspection types must be present for introspection queries to work
allTypeNames.contains("__Schema")
allTypeNames.contains("__Type")
allTypeNames.contains("__Field")
allTypeNames.contains("__InputValue")
allTypeNames.contains("__EnumValue")
allTypeNames.contains("__Directive")
allTypeNames.contains("__TypeKind")
allTypeNames.contains("__DirectiveLocation")
}

def "should include String and Boolean scalars in getAllTypesAsList"() {
given:
def sdl = '''
type Query {
id: ID
}
'''

when:
def schema = new FastSchemaGenerator().makeExecutableSchema(
new SchemaParser().parse(sdl),
RuntimeWiring.MOCKED_WIRING
)
def allTypeNames = schema.getAllTypesAsList().collect { it.name } as Set

then:
// String and Boolean are required by introspection types
// (__Type.name, __Field.name, etc. return String; __Field.isDeprecated returns Boolean)
allTypeNames.contains("String")
allTypeNames.contains("Boolean")
}

def "introspection types should match standard SchemaGenerator"() {
given:
def sdl = '''
type Query {
hello: String
}
'''
def registry = new SchemaParser().parse(sdl)

when:
def standardSchema = new SchemaGenerator().makeExecutableSchema(registry, RuntimeWiring.MOCKED_WIRING)
def fastSchema = new FastSchemaGenerator().makeExecutableSchema(registry, RuntimeWiring.MOCKED_WIRING)

def standardTypeNames = standardSchema.getAllTypesAsList().collect { it.name } as Set
def fastTypeNames = fastSchema.getAllTypesAsList().collect { it.name } as Set

then:
// FastBuilder should include all introspection types that standard builder includes
standardTypeNames.findAll { it.startsWith("__") }.each { introspectionType ->
assert fastTypeNames.contains(introspectionType) : "Missing introspection type: $introspectionType"
}
// FastBuilder should not contain introspection types not in standard builder
fastTypeNames.findAll { it.startsWith("__") }.each { introspectionType ->
assert standardTypeNames.contains(introspectionType) : "Extra introspection type: $introspectionType"
}
}
}