Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c30d5e2
add walker id with profile
devsergiy Oct 7, 2025
d352cf1
calculate keys only for root nodes
devsergiy Oct 7, 2025
2198c4e
local suggestions index
devsergiy Oct 7, 2025
582d23f
persist nodes collector
devsergiy Oct 7, 2025
17accc2
use longer ast path in walker
devsergiy Oct 8, 2025
f708759
skip irrelevant tests
devsergiy Oct 8, 2025
bd2cee5
tmp: walk only new field refs
devsergiy Oct 8, 2025
36213c4
do not use resolve field path
devsergiy Oct 8, 2025
0675f80
use tree walk instead of ast walk - requires+provides is broken
devsergiy Oct 8, 2025
5e317a2
tmp: root nodes handles index
devsergiy Oct 8, 2025
eeef577
cache interface implemented by objects
devsergiy Oct 8, 2025
6535204
create per datasource fields collectors once
devsergiy Oct 17, 2025
13f7393
fix tests
devsergiy Oct 18, 2025
1c88f3a
Revert "tmp: root nodes handles index"
devsergiy Oct 24, 2025
dda4f02
add bfs tree traverse using iterator
devsergiy Oct 24, 2025
5cdca68
fix size of node visit tasks, pass item ids directly instead of tree …
devsergiy Oct 24, 2025
51c3983
cleanup according to review comments
devsergiy Oct 24, 2025
9e45f5d
comment out profiling code in ast walker
devsergiy Oct 24, 2025
c4fd1a9
fix linter warning
devsergiy Oct 24, 2025
4ca2205
fix skipped tests
devsergiy Oct 24, 2025
19743db
add back semaphore
devsergiy Oct 24, 2025
ce1ecf2
cleanup
devsergiy Oct 24, 2025
fd87f01
fix an edge case when entity is a child node
devsergiy Oct 24, 2025
0305727
fix an edge case
devsergiy Oct 24, 2025
41b3461
fix collecting keys for an abstract type members
devsergiy Oct 29, 2025
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
76 changes: 52 additions & 24 deletions v2/pkg/ast/ast_interface_type_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ import (
// name: String
// }
type InterfaceTypeDefinition struct {
Description Description // optional, describes the interface
InterfaceLiteral position.Position // interface
Name ByteSliceReference // e.g. NamedEntity
ImplementsInterfaces TypeList // e.g implements Bar & Baz
HasDirectives bool
Directives DirectiveList // optional, e.g. @foo
HasFieldDefinitions bool
FieldsDefinition FieldDefinitionList // optional, e.g. { name: String }
Description Description // optional, describes the interface
InterfaceLiteral position.Position // interface
Name ByteSliceReference // e.g. NamedEntity
ImplementsInterfaces TypeList // e.g implements Bar & Baz
HasDirectives bool
Directives DirectiveList // optional, e.g. @foo
HasFieldDefinitions bool
FieldsDefinition FieldDefinitionList // optional, e.g. { name: String }
ImplementedByObjectDefinitions []int // list of ObjectTypeDefinition refs that implement this interface
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

func (d *Document) InterfaceTypeDefinitionNameBytes(ref int) ByteSlice {
Expand Down Expand Up @@ -163,15 +164,21 @@ func (d *Document) InterfaceTypeDefinitionFieldWithName(ref int, fieldName []byt

// InterfaceTypeDefinitionImplementedByObjectWithNames returns object type names implementing the interface.
func (d *Document) InterfaceTypeDefinitionImplementedByObjectWithNames(interfaceDefRef int) (typeNames []string, ok bool) {
implementedByNodes := d.InterfaceTypeDefinitionImplementedByRootNodes(interfaceDefRef)

typeNames = make([]string, 0, len(implementedByNodes))
for _, implementedByNode := range implementedByNodes {
if implementedByNode.Kind != NodeKindObjectTypeDefinition {
continue
implementedByObjectRefs := d.InterfaceTypeDefinitions[interfaceDefRef].ImplementedByObjectDefinitions
if implementedByObjectRefs == nil {
// fallback for documents not precalculated (e.g., built programmatically)
implementors := d.InterfaceTypeDefinitionImplementedByRootNodes(interfaceDefRef)
typeNames = make([]string, 0, len(implementors))
for _, n := range implementors {
if n.Kind == NodeKindObjectTypeDefinition {
typeNames = append(typeNames, d.ObjectTypeDefinitionNameString(n.Ref))
}
}
} else {
typeNames = make([]string, 0, len(implementedByObjectRefs))
for _, implementedByObjectRef := range d.InterfaceTypeDefinitions[interfaceDefRef].ImplementedByObjectDefinitions {
typeNames = append(typeNames, d.ObjectTypeDefinitionNameString(implementedByObjectRef))
}

typeNames = append(typeNames, implementedByNode.NameString(d))
}

if len(typeNames) > 0 {
Expand All @@ -183,15 +190,21 @@ func (d *Document) InterfaceTypeDefinitionImplementedByObjectWithNames(interface
}

func (d *Document) InterfaceTypeDefinitionImplementedByObjectWithNamesAsBytes(interfaceDefRef int) (typeNames [][]byte, ok bool) {
implementedByNodes := d.InterfaceTypeDefinitionImplementedByRootNodes(interfaceDefRef)

typeNames = make([][]byte, 0, len(implementedByNodes))
for _, implementedByNode := range implementedByNodes {
if implementedByNode.Kind != NodeKindObjectTypeDefinition {
continue
implementedByObjectRefs := d.InterfaceTypeDefinitions[interfaceDefRef].ImplementedByObjectDefinitions
if implementedByObjectRefs == nil {
implementors := d.InterfaceTypeDefinitionImplementedByRootNodes(interfaceDefRef)
typeNames = make([][]byte, 0, len(implementors))
// fallback for documents not precalculated (e.g., built programmatically)
for _, n := range implementors {
if n.Kind == NodeKindObjectTypeDefinition {
typeNames = append(typeNames, d.ObjectTypeDefinitionNameBytes(n.Ref))
}
}
} else {
typeNames = make([][]byte, 0, len(implementedByObjectRefs))
for _, implementedByObjectRef := range d.InterfaceTypeDefinitions[interfaceDefRef].ImplementedByObjectDefinitions {
typeNames = append(typeNames, d.ObjectTypeDefinitionNameBytes(implementedByObjectRef))
}

typeNames = append(typeNames, implementedByNode.NameBytes(d))
}

if len(typeNames) > 0 {
Expand All @@ -201,3 +214,18 @@ func (d *Document) InterfaceTypeDefinitionImplementedByObjectWithNamesAsBytes(in

return nil, false
}

func (d *Document) PopulateInterfaceTypeDefinitionImplementedByObjects() {
for ref := 0; ref < len(d.InterfaceTypeDefinitions); ref++ {
implementedByNodes := d.InterfaceTypeDefinitionImplementedByRootNodes(ref)
implementedByObjectRefs := make([]int, 0, len(implementedByNodes))
for _, implementedByNode := range implementedByNodes {
if implementedByNode.Kind != NodeKindObjectTypeDefinition {
continue
}

implementedByObjectRefs = append(implementedByObjectRefs, implementedByNode.Ref)
}
d.InterfaceTypeDefinitions[ref].ImplementedByObjectDefinitions = implementedByObjectRefs
}
}
2 changes: 1 addition & 1 deletion v2/pkg/astnormalization/abstract_field_normalizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type AbstractFieldNormalizer struct {
}

func NewAbstractFieldNormalizer(operation *ast.Document, definition *ast.Document, fieldRef int) *AbstractFieldNormalizer {
walker := astvisitor.NewWalker(48)
walker := astvisitor.NewWalkerWithID(48, "AbstractFieldNormalizer")

normalizer := &AbstractFieldNormalizer{
Walker: &walker,
Expand Down
26 changes: 13 additions & 13 deletions v2/pkg/astnormalization/astnormalization.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ func (o *OperationNormalizer) setupOperationWalkers() {
// NOTE: normalization rules for variables rely on the fact that
// we will visit only a single operation, so it is important to remove non-matching operations
if o.options.removeNotMatchingOperationDefinitions {
removeNotMatchingOperationDefinitionsWalker := astvisitor.NewWalker(2)
removeNotMatchingOperationDefinitionsWalker := astvisitor.NewWalkerWithID(2, "RemoveNotMatchingOperationDefinitions")
// This rule does not walk deep into ast, so a separate walk is not expensive,
// but we could not mix this walk with other rules, because they need to go deep
o.removeOperationDefinitionsVisitor = removeOperationDefinitions(&removeNotMatchingOperationDefinitionsWalker)
Expand All @@ -214,11 +214,11 @@ func (o *OperationNormalizer) setupOperationWalkers() {
})
}

directivesIncludeSkip := astvisitor.NewWalker(8)
directivesIncludeSkip := astvisitor.NewWalkerWithID(8, "DirectivesIncludeSkip")
preventFragmentCycles(&directivesIncludeSkip)
directiveIncludeSkipKeepNodes(&directivesIncludeSkip, o.options.ignoreSkipInclude)

cleanup := astvisitor.NewWalker(8)
cleanup := astvisitor.NewWalkerWithID(8, "Cleanup")
deduplicateFields(&cleanup)
if o.options.removeUnusedVariables {
del := deleteUnusedVariables(&cleanup)
Expand All @@ -235,7 +235,7 @@ func (o *OperationNormalizer) setupOperationWalkers() {
})

if o.options.inlineFragmentSpreads {
fragmentInline := astvisitor.NewWalker(8)
fragmentInline := astvisitor.NewWalkerWithID(8, "FragmentSpreadInline")
fragmentSpreadInline(&fragmentInline)
o.operationWalkers = append(o.operationWalkers, walkerStage{
name: "fragmentInline",
Expand All @@ -244,31 +244,31 @@ func (o *OperationNormalizer) setupOperationWalkers() {
}

if o.options.extractVariables {
extractVariablesWalker := astvisitor.NewWalker(8)
extractVariablesWalker := astvisitor.NewWalkerWithID(8, "ExtractVariables")
extractVariables(&extractVariablesWalker)
o.operationWalkers = append(o.operationWalkers, walkerStage{
name: "extractVariables",
walker: &extractVariablesWalker,
})
}

other := astvisitor.NewWalker(8)
other := astvisitor.NewWalkerWithID(8, "Other")
removeSelfAliasing(&other)
inlineSelectionsFromInlineFragments(&other)
o.operationWalkers = append(o.operationWalkers, walkerStage{
name: "removeSelfAliasing, inlineSelectionsFromInlineFragments",
walker: &other,
})

mergeInlineFragments := astvisitor.NewWalker(8)
mergeInlineFragments := astvisitor.NewWalkerWithID(8, "MergeInlineFragmentSelections")
mergeInlineFragmentSelections(&mergeInlineFragments)
o.operationWalkers = append(o.operationWalkers, walkerStage{
name: "mergeInlineFragmentSelections",
walker: &mergeInlineFragments,
})

if o.options.removeFragmentDefinitions {
removeFragments := astvisitor.NewWalker(8)
removeFragments := astvisitor.NewWalkerWithID(8, "RemoveFragmentDefinitions")
removeFragmentDefinitions(&removeFragments)

o.operationWalkers = append(o.operationWalkers, walkerStage{
Expand All @@ -283,7 +283,7 @@ func (o *OperationNormalizer) setupOperationWalkers() {
})

if o.options.extractVariables {
variablesProcessing := astvisitor.NewWalker(8)
variablesProcessing := astvisitor.NewWalkerWithID(8, "VariablesProcessing")
inputCoercionForList(&variablesProcessing)
extractVariablesDefaultValue(&variablesProcessing)
injectInputFieldDefaults(&variablesProcessing)
Expand Down Expand Up @@ -356,21 +356,21 @@ type VariablesNormalizer struct {
func NewVariablesNormalizer() *VariablesNormalizer {
// delete unused modifying variables refs,
// so it is safer to run it sequentially with the extraction
thirdDeleteUnused := astvisitor.NewWalker(8)
thirdDeleteUnused := astvisitor.NewWalkerWithID(8, "DeleteUnusedVariables")
del := deleteUnusedVariables(&thirdDeleteUnused)

// register variable usage detection on the first stage
// and pass usage information to the deletion visitor
// so it keeps variables that are defined but not used at all
// ensuring that validation can still catch them
firstDetectUnused := astvisitor.NewWalker(8)
firstDetectUnused := astvisitor.NewWalkerWithID(8, "DetectVariableUsage")
detectVariableUsage(&firstDetectUnused, del)

secondExtract := astvisitor.NewWalker(8)
secondExtract := astvisitor.NewWalkerWithID(8, "ExtractVariables")
variablesExtractionVisitor := extractVariables(&secondExtract)
extractVariablesDefaultValue(&secondExtract)

fourthCoerce := astvisitor.NewWalker(0)
fourthCoerce := astvisitor.NewWalkerWithID(0, "VariablesCoercion")
inputCoercionForList(&fourthCoerce)

return &VariablesNormalizer{
Expand Down
2 changes: 1 addition & 1 deletion v2/pkg/astnormalization/variables_mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type VariablesMapper struct {
}

func NewVariablesMapper() *VariablesMapper {
walker := astvisitor.NewDefaultWalker()
walker := astvisitor.NewDefaultWalkerWithID("VariablesMapper")
mapper := remapVariables(&walker)

return &VariablesMapper{
Expand Down
10 changes: 10 additions & 0 deletions v2/pkg/astparser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ func (p *Parser) Parse(document *ast.Document, report *operationreport.Report) {
p.report = report
p.tokenize()
p.parse()
p.precalculate()
}

func (p *Parser) tokenize() {
Expand All @@ -106,9 +107,18 @@ func (p *Parser) ParseWithLimits(limits TokenizerLimits, document *ast.Document,
return stats, err
}
p.parse()
p.precalculate()
return stats, nil
}

func (p *Parser) precalculate() {
if p.report.HasErrors() {
return
}

p.document.PopulateInterfaceTypeDefinitionImplementedByObjects()
}

func (p *Parser) parse() {
for {
key, literalReference := p.peekLiteral()
Expand Down
2 changes: 1 addition & 1 deletion v2/pkg/asttransform/typename_visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type TypeNameVisitor struct {
}

func NewTypeNameVisitor() *TypeNameVisitor {
walker := astvisitor.NewWalker(48)
walker := astvisitor.NewWalkerWithID(48, "TypeNameVisitor")

visitor := &TypeNameVisitor{
Walker: &walker,
Expand Down
4 changes: 2 additions & 2 deletions v2/pkg/astvalidation/operation_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func DefaultOperationValidator(options ...Option) *OperationValidator {
opt(&opts)
}
validator := OperationValidator{
walker: astvisitor.NewWalker(48),
walker: astvisitor.NewWalkerWithID(48, "OperationValidator"),
}

if opts.ApolloCompatibilityFlags.UseGraphQLValidationFailedStatus {
Expand Down Expand Up @@ -64,7 +64,7 @@ func DefaultOperationValidator(options ...Option) *OperationValidator {

func NewOperationValidator(rules []Rule) *OperationValidator {
validator := OperationValidator{
walker: astvisitor.NewWalker(48),
walker: astvisitor.NewWalkerWithID(48, "OperationValidator"),
}

for _, rule := range rules {
Expand Down
23 changes: 22 additions & 1 deletion v2/pkg/astvisitor/visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func newSkipVisitors(skips []int, planner interface{}, allowedToVisit bool) Skip
// Walker orchestrates the process of walking an AST and calling all registered callbacks
// Always use NewWalker to instantiate a new Walker
type Walker struct {
walkerID string
// Ancestors is the slice of Nodes to the current Node in a callback
// don't keep a reference to this slice, always copy it if you want to work with it after the callback returned
Ancestors []ast.Node
Expand Down Expand Up @@ -96,11 +97,21 @@ type Walker struct {
OnExternalError func(err *operationreport.ExternalError)
}

func NewWalkerWithID(ancestorSize int, id string) Walker {
return Walker{
Ancestors: make([]ast.Node, 0, ancestorSize),
Path: make([]ast.PathItem, 0, 64),
TypeDefinitions: make([]ast.Node, 0, ancestorSize),
deferred: make([]func(), 0, 8),
walkerID: id,
}
}

// NewWalker returns a fully initialized Walker
func NewWalker(ancestorSize int) Walker {
return Walker{
Ancestors: make([]ast.Node, 0, ancestorSize),
Path: make([]ast.PathItem, 0, ancestorSize),
Path: make([]ast.PathItem, 0, 64),
TypeDefinitions: make([]ast.Node, 0, ancestorSize),
deferred: make([]func(), 0, 8),
}
Expand All @@ -111,6 +122,10 @@ func NewDefaultWalker() Walker {
return NewWalker(8)
}

func NewDefaultWalkerWithID(id string) Walker {
return NewWalkerWithID(8, id)
}

var (
walkerPool = sync.Pool{}
)
Expand Down Expand Up @@ -1365,6 +1380,12 @@ func (w *Walker) SetVisitorFilter(filter VisitorFilter) {

// Walk initiates the walker to start walking the AST from the top root Node
func (w *Walker) Walk(document, definition *ast.Document, report *operationreport.Report) {
// uncomment to get walker labels in pprof profiles
// ctx := context.Background()
// labels := pprof.Labels("walker_id", w.walkerID)
// pprof.Do(ctx, labels, func(ctx context.Context) {
// })

if report == nil {
w.Report = &operationreport.Report{}
} else {
Expand Down
Loading