Skip to content

Commit

Permalink
Merge pull request #19 from jensneuse/add-printer
Browse files Browse the repository at this point in the history
Add AST printer
  • Loading branch information
jensneuse authored Mar 2, 2019
2 parents 85a5af4 + a774669 commit 84e7c3c
Show file tree
Hide file tree
Showing 25 changed files with 1,255 additions and 296 deletions.
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,25 @@ BenchmarkParser-4 50000 29176 ns/op 0 B/op 0 allocs/op

```
pkg: github.com/jensneuse/graphql-go-tools/pkg/validator
BenchmarkValidator/test_valid_schema-4 200000 7823 ns/op 0 B/op 0 allocs/op
BenchmarkValidator/test_valid_schema-4 200000 7836 ns/op 0 B/op 0 allocs/op
BenchmarkValidator/test_valid_schema-4 200000 7766 ns/op 0 B/op 0 allocs/op
BenchmarkValidator/test_valid_schema-4 200000 7777 ns/op 0 B/op 0 allocs/op
BenchmarkValidator/introspection_query-4 3000 407511 ns/op 44 B/op 0 allocs/op
BenchmarkValidator/introspection_query-4 3000 410118 ns/op 44 B/op 0 allocs/op
BenchmarkValidator/introspection_query-4 3000 405893 ns/op 45 B/op 0 allocs/op
BenchmarkValidator/introspection_query-4 3000 403380 ns/op 56 B/op 0 allocs/op
BenchmarkValidator/test_valid_schema-4 200000 6091 ns/op 0 B/op 0 allocs/op
BenchmarkValidator/test_valid_schema-4 200000 6174 ns/op 0 B/op 0 allocs/op
BenchmarkValidator/test_valid_schema-4 200000 6119 ns/op 0 B/op 0 allocs/op
BenchmarkValidator/test_valid_schema-4 200000 5975 ns/op 0 B/op 0 allocs/op
BenchmarkValidator/introspection_query-4 20000 86069 ns/op 2 B/op 0 allocs/op
BenchmarkValidator/introspection_query-4 20000 88226 ns/op 4 B/op 0 allocs/op
BenchmarkValidator/introspection_query-4 20000 88185 ns/op 2 B/op 0 allocs/op
BenchmarkValidator/introspection_query-4 20000 88447 ns/op 2 B/op 0 allocs/op
```

To put these numbers into perspective. Parsing + validating the (quite complex) introspection query is < 0.5ms (on my 2013 MacBook) which should be acceptable for web applications.
To put these numbers into perspective. Parsing + validating the (quite complex) introspection query is ~ 0.1ms (on my 2013 MacBook) which should be acceptable for web applications.

It's important to note that gc is kept at a minimum which should enable applications built on top of this library to have almost zero deviation regarding latency.

You'll probably add bottlenecks at another layer, e.g. invoking a database.
You'll probably add bottlenecks at another layer, e.g. invoking a database.

While adding the ast printing feature I found a severe logical error that made the walker walk way too many times the same values.
I've refactored the validator to properly handle fragment spreads which also helped to easily find the operation definitions a fragment is used by.
By fixing this issue validation time for the introspection query dropped from ~ 407k ns to ~ 88k ns.

## Contributors

Expand Down
1 change: 1 addition & 0 deletions pkg/document/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type Value struct {
ValueType ValueType
Reference int
Position position.Position
Raw ByteSliceReference
}

func (v Value) NodeSelectionSet() int {
Expand Down
9 changes: 9 additions & 0 deletions pkg/lexing/position/position.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,12 @@ func (p *Position) MergeEndIntoEnd(position Position) {
p.LineEnd = position.LineEnd
p.CharEnd = position.CharEnd
}

func (p *Position) IsSet() bool {
return p.CharStart != 0 || p.CharEnd != 0 || p.LineStart != 0 || p.LineEnd != 0
}

func (p *Position) IsBefore(another Position) bool {
return p.LineEnd < another.LineStart ||
p.LineEnd == another.LineStart && p.CharEnd < another.CharStart
}
14 changes: 14 additions & 0 deletions pkg/lookup/iterable.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ func (w *Walker) newIterable(refs []int) Iterable {
}
}

type FragmentDefinitionIterable struct {
Iterable
}

func (f *FragmentDefinitionIterable) Value() document.FragmentDefinition {
return f.w.l.FragmentDefinition(f.node().Ref)
}

func (w *Walker) FragmentDefinitionIterable() FragmentDefinitionIterable {
return FragmentDefinitionIterable{
Iterable: w.newIterable(w.c.fragmentDefinitions),
}
}

type OperationDefinitionIterable struct {
Iterable
}
Expand Down
80 changes: 80 additions & 0 deletions pkg/lookup/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ func (l *Lookup) initRefsFromCache(refs *[]int) {
*refs = l.refCache[:0]
}

func (l *Lookup) HasOperationDefinitions() bool {
return len(l.p.ParsedDefinitions.OperationDefinitions) > 0
}

func (l *Lookup) HasFragmentDefinitions() bool {
return len(l.p.ParsedDefinitions.FragmentDefinitions) > 0
}

func (l *Lookup) OperationDefinition(i int) document.OperationDefinition {
return l.p.ParsedDefinitions.OperationDefinitions[i]
}
Expand Down Expand Up @@ -291,6 +299,74 @@ func (l *Lookup) FieldsIterator(i []int) FieldsIterator {
}
}

type SelectionSetContentsIterator struct {
set document.SelectionSet
kind NodeKind
ref int
l *Lookup
}

func (s *SelectionSetContentsIterator) Next() bool {

var field document.Field
var inline document.InlineFragment
var spread document.FragmentSpread

hasField := len(s.set.Fields) > 0
hasInline := len(s.set.InlineFragments) > 0
hasSpread := len(s.set.FragmentSpreads) > 0

if hasField {
field = s.l.Field(s.set.Fields[0])
}
if hasInline {
inline = s.l.InlineFragment(s.set.InlineFragments[0])
}
if hasSpread {
spread = s.l.FragmentSpread(s.set.FragmentSpreads[0])
}

if hasField {
if !hasSpread || hasSpread && field.Position.IsBefore(spread.Position) {
if !hasInline || hasInline && field.Position.IsBefore(inline.Position) {
s.kind = FIELD
s.ref = s.set.Fields[0]
s.set.Fields = s.set.Fields[1:]
return true
}
}
}

if hasInline {
if !hasSpread || hasSpread && inline.Position.IsBefore(spread.Position) {
s.kind = INLINE_FRAGMENT
s.ref = s.set.InlineFragments[0]
s.set.InlineFragments = s.set.InlineFragments[1:]
return true
}
}

if hasSpread {
s.kind = FRAGMENT_SPREAD
s.ref = s.set.FragmentSpreads[0]
s.set.FragmentSpreads = s.set.FragmentSpreads[1:]
return true
}

return false
}

func (s *SelectionSetContentsIterator) Value() (kind NodeKind, ref int) {
return s.kind, s.ref
}

func (l *Lookup) SelectionSetContentsIterator(ref int) SelectionSetContentsIterator {
return SelectionSetContentsIterator{
l: l,
set: l.SelectionSet(ref),
}
}

type SelectionSetFieldsIterator struct {
l *Lookup
setTypeName int
Expand Down Expand Up @@ -648,6 +724,10 @@ func (l *Lookup) ValueObjectsAreEqual(first, second int) bool {
return true
}

func (l *Lookup) ObjectValue(ref int) document.ObjectValue {
return l.p.ParsedDefinitions.ObjectValues[ref]
}

func (l *Lookup) ObjectField(i int) document.ObjectField {
return l.p.ParsedDefinitions.ObjectFields[i]
}
Expand Down
55 changes: 1 addition & 54 deletions pkg/lookup/lookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,59 +104,6 @@ func TestLookup(t *testing.T) {
}
}

/* wantSetWithSubsets := func(name string, subsets ..._wantSet) _wantSetWithSubsets {
return _wantSetWithSubsets{
name: name,
subSets: subsets,
}
}
mustHaveSelectionSetsWithDifferingSubSets := func(sets ..._wantSetWithSubsets) checkFunc {
return func(walker *Walker) {
iter := walker.SelectionSetIterable()
for _, want := range sets {
if !iter.Next() {
panic("mustHaveSelectionSetsWithDifferingSubSets: want next set, got nothing")
}
set, _, parent := iter.Value()
typeName := walker.SelectionSetTypeName(set, parent)
got := string(walker.l.CachedName(typeName))
if want.name != got {
panic(fmt.Errorf("mustHaveSelectionSetsWithDifferingSubSets: want type name: %s, got: %s", want, got))
}
subSets := walker.l.SelectionSetDifferingSelectionSetIterator(set, typeName)
for _, wantSubset := range want.subSets {
if !subSets.Next() {
panic(fmt.Errorf("mustHaveSelectionSetsWithDifferingSubSets: want next subset, got nothing"))
}
typedSet := subSets.Value()
gotName := string(walker.l.CachedName(typedSet.Type.Name))
if gotName != wantSubset.name {
panic(fmt.Errorf("mustHaveSelectionSetsWithDifferingSubSets: want subSet with type: %s, got: %s", wantSubset.name, gotName))
}
fields := walker.l.SelectionSetCollectedFields(set, typeName)
for _, wantField := range wantSubset.fields {
if !fields.Next() {
panic(fmt.Errorf("mustHaveSelectionSetsWithDifferingSubSets: want next field (%s), got nothing", wantField))
}
gotField := fields.Value()
fieldName := string(walker.l.CachedName(gotField.Name))
if fieldName != wantField {
panic(fmt.Errorf("mustHaveSelectionSetsWithDifferingSubSets: want field: %s, got: %s", wantField, fieldName))
}
}
if fields.Next() {
next := string(walker.l.CachedName(fields.Value().Name))
panic(fmt.Errorf("mustHaveSelectionSetsWithDifferingSubSets: want Next() to return false, got next field: %s", next))
}
}
}
}
}*/

t.Run("lookup SelectionSetTypeName", func(t *testing.T) {
t.Run("on fragment definition", func(t *testing.T) {
run(testDefinition, `fragment conflictingBecauseAlias on Dog {
Expand Down Expand Up @@ -207,7 +154,7 @@ func TestLookup(t *testing.T) {
fragment nameFrag on Dog {
name
}`,
mustHaveSelectionSetTypeNames("Dog", "Query", "Dog", "Dog"),
mustHaveSelectionSetTypeNames("Dog", "Query", "Dog"),
)
})
})
Expand Down
Loading

0 comments on commit 84e7c3c

Please sign in to comment.