From 58408eadc1fa150222caf6ed1dba562d2eec682b Mon Sep 17 00:00:00 2001 From: Vikram Rangnekar Date: Tue, 14 May 2019 22:32:12 -0400 Subject: [PATCH] Use hash's as ids for table relationships --- psql/psql.go | 15 +++++-------- psql/psql_test.go | 2 +- psql/tables.go | 38 +++++++++++++++++-------------- qcode/parse_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++++ qcode/qcode.go | 12 ++++++++++ serv/core.go | 29 +++++++++++++----------- serv/reso.go | 7 +++++- 7 files changed, 116 insertions(+), 41 deletions(-) diff --git a/psql/psql.go b/psql/psql.go index 211ac1d..3026259 100644 --- a/psql/psql.go +++ b/psql/psql.go @@ -30,7 +30,7 @@ func NewCompiler(conf Config) *Compiler { return &Compiler{conf.Schema, conf.Vars, conf.TableMap} } -func (c *Compiler) AddRelationship(key TTKey, val *DBRel) { +func (c *Compiler) AddRelationship(key uint64, val *DBRel) { c.schema.RelMap[key] = val } @@ -139,9 +139,8 @@ func (v *selectBlock) processChildren() (uint32, []*qcode.Column) { for _, id := range v.sel.Children { child := &v.qc.Query.Selects[id] - k := TTKey{child.Table, v.sel.Table} - rel, ok := v.schema.RelMap[k] + rel, ok := v.schema.RelMap[child.RelID] if !ok { skipped |= (1 << uint(id)) continue @@ -290,8 +289,7 @@ func (v *joinClose) render(w io.Writer) error { } func (v *selectBlock) renderJoinTable(w io.Writer) { - k := TTKey{v.sel.Table, v.parent.Table} - rel, ok := v.schema.RelMap[k] + rel, ok := v.schema.RelMap[v.sel.RelID] if !ok { panic(errors.New("no relationship found")) } @@ -315,14 +313,12 @@ func (v *selectBlock) renderColumns(w io.Writer) { } func (v *selectBlock) renderRemoteRelColumns(w io.Writer) { - k := TTKey{Table2: v.sel.Table} i := 0 for _, id := range v.sel.Children { child := &v.qc.Query.Selects[id] - k.Table1 = child.Table - rel, ok := v.schema.RelMap[k] + rel, ok := v.schema.RelMap[child.RelID] if !ok || rel.Type != RelRemote { continue } @@ -489,8 +485,7 @@ func (v *selectBlock) renderOrderByColumns(w io.Writer) { } func (v *selectBlock) renderRelationship(w io.Writer) { - k := TTKey{v.sel.Table, v.parent.Table} - rel, ok := v.schema.RelMap[k] + rel, ok := v.schema.RelMap[v.sel.RelID] if !ok { panic(errors.New("no relationship found")) } diff --git a/psql/psql_test.go b/psql/psql_test.go index 4c8d295..db4c85c 100644 --- a/psql/psql_test.go +++ b/psql/psql_test.go @@ -100,7 +100,7 @@ func TestMain(m *testing.M) { schema := &DBSchema{ Tables: make(map[string]*DBTableInfo), - RelMap: make(map[TTKey]*DBRel), + RelMap: make(map[uint64]*DBRel), } for i, t := range tables { diff --git a/psql/tables.go b/psql/tables.go index 779034c..80839b8 100644 --- a/psql/tables.go +++ b/psql/tables.go @@ -4,20 +4,13 @@ import ( "fmt" "strings" + "github.com/cespare/xxhash/v2" "github.com/go-pg/pg" ) -type TCKey struct { - Table, Column string -} - -type TTKey struct { - Table1, Table2 string -} - type DBSchema struct { Tables map[string]*DBTableInfo - RelMap map[TTKey]*DBRel + RelMap map[uint64]*DBRel } type DBTableInfo struct { @@ -47,7 +40,7 @@ type DBRel struct { func NewDBSchema(db *pg.DB) (*DBSchema, error) { schema := &DBSchema{ Tables: make(map[string]*DBTableInfo), - RelMap: make(map[TTKey]*DBRel), + RelMap: make(map[uint64]*DBRel), } tables, err := GetTables(db) @@ -87,6 +80,8 @@ func (s *DBSchema) updateSchema(t *DBTable, cols []*DBColumn) { ct := strings.ToLower(t.Name) s.Tables[ct] = ti + h := xxhash.New() + for _, c := range cols { switch { case c.Type == "tsvector": @@ -110,12 +105,12 @@ func (s *DBSchema) updateSchema(t *DBTable, cols []*DBColumn) { // Belongs-to relation between current table and the // table in the foreign key rel1 := &DBRel{RelBelongTo, "", "", c.Name, fc.Name} - s.RelMap[TTKey{ct, ft}] = rel1 + s.RelMap[relID(h, ct, ft)] = rel1 // One-to-many relation between the foreign key table and the // the current table rel2 := &DBRel{RelOneToMany, "", "", fc.Name, c.Name} - s.RelMap[TTKey{ft, ct}] = rel2 + s.RelMap[relID(h, ft, ct)] = rel2 jcols = append(jcols, c) } @@ -131,7 +126,7 @@ func (s *DBSchema) updateSchema(t *DBTable, cols []*DBColumn) { for i := range jcols { for n := range jcols { if n != i { - s.updateSchemaOTMT(ct, jcols[i], jcols[n], colByID) + s.updateSchemaOTMT(h, ct, jcols[i], jcols[n], colByID) } } } @@ -139,7 +134,10 @@ func (s *DBSchema) updateSchema(t *DBTable, cols []*DBColumn) { } func (s *DBSchema) updateSchemaOTMT( - ct string, col1, col2 *DBColumn, colByID map[int]*DBColumn) { + h *xxhash.Digest, + ct string, + col1, col2 *DBColumn, + colByID map[int]*DBColumn) { t1 := strings.ToLower(col1.FKeyTable) t2 := strings.ToLower(col2.FKeyTable) @@ -157,13 +155,13 @@ func (s *DBSchema) updateSchemaOTMT( // 2nd foreign key table //rel1 := &DBRel{RelOneToManyThrough, ct, fc1.Name, col1.Name} rel1 := &DBRel{RelOneToManyThrough, ct, col2.Name, fc2.Name, col1.Name} - s.RelMap[TTKey{t1, t2}] = rel1 + s.RelMap[relID(h, t1, t2)] = rel1 // One-to-many-through relation between 2nd foreign key table and the // 1nd foreign key table //rel2 := &DBRel{RelOneToManyThrough, ct, col2.Name, fc2.Name} rel2 := &DBRel{RelOneToManyThrough, ct, col1.Name, fc1.Name, col2.Name} - s.RelMap[TTKey{t2, t1}] = rel2 + s.RelMap[relID(h, t2, t1)] = rel2 } type DBTable struct { @@ -264,3 +262,11 @@ func (s *DBSchema) GetTable(table string) (*DBTableInfo, error) { } return t, nil } + +func relID(h *xxhash.Digest, child, parent string) uint64 { + h.WriteString(child) + h.WriteString(parent) + v := h.Sum64() + h.Reset() + return v +} diff --git a/qcode/parse_test.go b/qcode/parse_test.go index 2854e07..ecc9d5e 100644 --- a/qcode/parse_test.go +++ b/qcode/parse_test.go @@ -73,3 +73,57 @@ func TestEmptyCompile(t *testing.T) { t.Fatal(errors.New("expecting an error")) } } + +func BenchmarkQCompile(b *testing.B) { + qcompile, _ := NewCompiler(Config{}) + + val := `query { + products( + where: { + and: { + not: { id: { is_null: true } }, + price: { gt: 10 } + }}) { + id + name + price + } + }` + + b.ResetTimer() + b.ReportAllocs() + + for n := 0; n < b.N; n++ { + _, err := qcompile.CompileQuery(val) + + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkLex(b *testing.B) { + val := `query { + products( + where: { + and: { + not: { id: { is_null: true } }, + price: { gt: 10 } + }}) { + id + name + price + } + }` + + b.ResetTimer() + b.ReportAllocs() + + for n := 0; n < b.N; n++ { + _, err := lex(val) + + if err != nil { + b.Fatal(err) + } + } +} diff --git a/qcode/qcode.go b/qcode/qcode.go index 52b1aca..6c17ccf 100644 --- a/qcode/qcode.go +++ b/qcode/qcode.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/cespare/xxhash/v2" "github.com/dosco/super-graph/util" "github.com/gobuffalo/flect" ) @@ -30,6 +31,7 @@ type Column struct { type Select struct { ID uint16 ParentID uint16 + RelID uint64 Args map[string]*Node AsList bool Table string @@ -255,6 +257,7 @@ func (com *Compiler) compileQuery(op *Operation) (*Query, error) { selects := make([]Select, 0, 5) st := util.NewStack() + h := xxhash.New() if len(op.Fields) == 0 { return nil, errors.New("empty query") @@ -294,6 +297,7 @@ func (com *Compiler) compileQuery(op *Operation) (*Query, error) { if s.ID != 0 { p := &selects[s.ParentID] p.Children = append(p.Children, s.ID) + s.RelID = relID(h, tn, p.Table) } if fn == tn { @@ -875,3 +879,11 @@ func buildPath(a []string) string { } return b.String() } + +func relID(h *xxhash.Digest, child, parent string) uint64 { + h.WriteString(child) + h.WriteString(parent) + v := h.Sum64() + h.Reset() + return v +} diff --git a/serv/core.go b/serv/core.go index e8e4f58..51eaa90 100644 --- a/serv/core.go +++ b/serv/core.go @@ -34,8 +34,6 @@ type coreContext struct { func (c *coreContext) handleReq(w io.Writer, req *http.Request) error { var err error - //cacheEnabled := (conf.EnableTracing == false) - qc, err := qcompile.CompileQuery(c.req.Query) if err != nil { return err @@ -130,21 +128,16 @@ func (c *coreContext) handleReq(w io.Writer, req *http.Request) error { return err } - // if cacheEnabled { - // if err = cache.Set(key, []byte(finalSQL)); err != nil { - // return err - // } - // } - return c.render(w, ob.Bytes()) } func (c *coreContext) resolveSQL(qc *qcode.QCode, vars variables) ( []byte, uint32, error) { - //var entry []byte - //var key string - //cacheEnabled := (conf.EnableTracing == false) + // var entry []byte + // var key string + + // cacheEnabled := (conf.EnableTracing == false) // if cacheEnabled { // k := sha1.Sum([]byte(req.Query)) @@ -180,13 +173,23 @@ func (c *coreContext) resolveSQL(qc *qcode.QCode, vars variables) ( return nil, 0, err } - finalSQL := sqlStmt.String() + finalSQL := sqlStmt.Bytes() if conf.DebugLevel > 0 { fmt.Println(finalSQL) } - st := time.Now() + // if cacheEnabled { + // if err = cache.Set(key, finalSQL); err != nil { + // return err + // } + // } + + var st time.Time + + if conf.EnableTracing { + st = time.Now() + } var root json.RawMessage _, err = db.Query(pg.Scan(&root), finalSQL) diff --git a/serv/reso.go b/serv/reso.go index 4e208ee..76da0d2 100644 --- a/serv/reso.go +++ b/serv/reso.go @@ -45,7 +45,12 @@ func initRemotes(t configTable) { // register a relationship between the remote data // and the database table - key := psql.TTKey{strings.ToLower(r.Name), t.Name} + + h.WriteString(strings.ToLower(r.Name)) + h.WriteString(t.Name) + key := h.Sum64() + h.Reset() + val := &psql.DBRel{ Type: psql.RelRemote, Col1: idcol,