Use hash's as ids for table relationships
This commit is contained in:
parent
e1d3bb9055
commit
58408eadc1
15
psql/psql.go
15
psql/psql.go
|
@ -30,7 +30,7 @@ func NewCompiler(conf Config) *Compiler {
|
||||||
return &Compiler{conf.Schema, conf.Vars, conf.TableMap}
|
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
|
c.schema.RelMap[key] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,9 +139,8 @@ func (v *selectBlock) processChildren() (uint32, []*qcode.Column) {
|
||||||
|
|
||||||
for _, id := range v.sel.Children {
|
for _, id := range v.sel.Children {
|
||||||
child := &v.qc.Query.Selects[id]
|
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 {
|
if !ok {
|
||||||
skipped |= (1 << uint(id))
|
skipped |= (1 << uint(id))
|
||||||
continue
|
continue
|
||||||
|
@ -290,8 +289,7 @@ func (v *joinClose) render(w io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *selectBlock) renderJoinTable(w io.Writer) {
|
func (v *selectBlock) renderJoinTable(w io.Writer) {
|
||||||
k := TTKey{v.sel.Table, v.parent.Table}
|
rel, ok := v.schema.RelMap[v.sel.RelID]
|
||||||
rel, ok := v.schema.RelMap[k]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(errors.New("no relationship found"))
|
panic(errors.New("no relationship found"))
|
||||||
}
|
}
|
||||||
|
@ -315,14 +313,12 @@ func (v *selectBlock) renderColumns(w io.Writer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *selectBlock) renderRemoteRelColumns(w io.Writer) {
|
func (v *selectBlock) renderRemoteRelColumns(w io.Writer) {
|
||||||
k := TTKey{Table2: v.sel.Table}
|
|
||||||
i := 0
|
i := 0
|
||||||
|
|
||||||
for _, id := range v.sel.Children {
|
for _, id := range v.sel.Children {
|
||||||
child := &v.qc.Query.Selects[id]
|
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 {
|
if !ok || rel.Type != RelRemote {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -489,8 +485,7 @@ func (v *selectBlock) renderOrderByColumns(w io.Writer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *selectBlock) renderRelationship(w io.Writer) {
|
func (v *selectBlock) renderRelationship(w io.Writer) {
|
||||||
k := TTKey{v.sel.Table, v.parent.Table}
|
rel, ok := v.schema.RelMap[v.sel.RelID]
|
||||||
rel, ok := v.schema.RelMap[k]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(errors.New("no relationship found"))
|
panic(errors.New("no relationship found"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ func TestMain(m *testing.M) {
|
||||||
|
|
||||||
schema := &DBSchema{
|
schema := &DBSchema{
|
||||||
Tables: make(map[string]*DBTableInfo),
|
Tables: make(map[string]*DBTableInfo),
|
||||||
RelMap: make(map[TTKey]*DBRel),
|
RelMap: make(map[uint64]*DBRel),
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, t := range tables {
|
for i, t := range tables {
|
||||||
|
|
|
@ -4,20 +4,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cespare/xxhash/v2"
|
||||||
"github.com/go-pg/pg"
|
"github.com/go-pg/pg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TCKey struct {
|
|
||||||
Table, Column string
|
|
||||||
}
|
|
||||||
|
|
||||||
type TTKey struct {
|
|
||||||
Table1, Table2 string
|
|
||||||
}
|
|
||||||
|
|
||||||
type DBSchema struct {
|
type DBSchema struct {
|
||||||
Tables map[string]*DBTableInfo
|
Tables map[string]*DBTableInfo
|
||||||
RelMap map[TTKey]*DBRel
|
RelMap map[uint64]*DBRel
|
||||||
}
|
}
|
||||||
|
|
||||||
type DBTableInfo struct {
|
type DBTableInfo struct {
|
||||||
|
@ -47,7 +40,7 @@ type DBRel struct {
|
||||||
func NewDBSchema(db *pg.DB) (*DBSchema, error) {
|
func NewDBSchema(db *pg.DB) (*DBSchema, error) {
|
||||||
schema := &DBSchema{
|
schema := &DBSchema{
|
||||||
Tables: make(map[string]*DBTableInfo),
|
Tables: make(map[string]*DBTableInfo),
|
||||||
RelMap: make(map[TTKey]*DBRel),
|
RelMap: make(map[uint64]*DBRel),
|
||||||
}
|
}
|
||||||
|
|
||||||
tables, err := GetTables(db)
|
tables, err := GetTables(db)
|
||||||
|
@ -87,6 +80,8 @@ func (s *DBSchema) updateSchema(t *DBTable, cols []*DBColumn) {
|
||||||
ct := strings.ToLower(t.Name)
|
ct := strings.ToLower(t.Name)
|
||||||
s.Tables[ct] = ti
|
s.Tables[ct] = ti
|
||||||
|
|
||||||
|
h := xxhash.New()
|
||||||
|
|
||||||
for _, c := range cols {
|
for _, c := range cols {
|
||||||
switch {
|
switch {
|
||||||
case c.Type == "tsvector":
|
case c.Type == "tsvector":
|
||||||
|
@ -110,12 +105,12 @@ func (s *DBSchema) updateSchema(t *DBTable, cols []*DBColumn) {
|
||||||
// Belongs-to relation between current table and the
|
// Belongs-to relation between current table and the
|
||||||
// table in the foreign key
|
// table in the foreign key
|
||||||
rel1 := &DBRel{RelBelongTo, "", "", c.Name, fc.Name}
|
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
|
// One-to-many relation between the foreign key table and the
|
||||||
// the current table
|
// the current table
|
||||||
rel2 := &DBRel{RelOneToMany, "", "", fc.Name, c.Name}
|
rel2 := &DBRel{RelOneToMany, "", "", fc.Name, c.Name}
|
||||||
s.RelMap[TTKey{ft, ct}] = rel2
|
s.RelMap[relID(h, ft, ct)] = rel2
|
||||||
|
|
||||||
jcols = append(jcols, c)
|
jcols = append(jcols, c)
|
||||||
}
|
}
|
||||||
|
@ -131,7 +126,7 @@ func (s *DBSchema) updateSchema(t *DBTable, cols []*DBColumn) {
|
||||||
for i := range jcols {
|
for i := range jcols {
|
||||||
for n := range jcols {
|
for n := range jcols {
|
||||||
if n != i {
|
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(
|
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)
|
t1 := strings.ToLower(col1.FKeyTable)
|
||||||
t2 := strings.ToLower(col2.FKeyTable)
|
t2 := strings.ToLower(col2.FKeyTable)
|
||||||
|
@ -157,13 +155,13 @@ func (s *DBSchema) updateSchemaOTMT(
|
||||||
// 2nd foreign key table
|
// 2nd foreign key table
|
||||||
//rel1 := &DBRel{RelOneToManyThrough, ct, fc1.Name, col1.Name}
|
//rel1 := &DBRel{RelOneToManyThrough, ct, fc1.Name, col1.Name}
|
||||||
rel1 := &DBRel{RelOneToManyThrough, ct, col2.Name, fc2.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
|
// One-to-many-through relation between 2nd foreign key table and the
|
||||||
// 1nd foreign key table
|
// 1nd foreign key table
|
||||||
//rel2 := &DBRel{RelOneToManyThrough, ct, col2.Name, fc2.Name}
|
//rel2 := &DBRel{RelOneToManyThrough, ct, col2.Name, fc2.Name}
|
||||||
rel2 := &DBRel{RelOneToManyThrough, ct, col1.Name, fc1.Name, col2.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 {
|
type DBTable struct {
|
||||||
|
@ -264,3 +262,11 @@ func (s *DBSchema) GetTable(table string) (*DBTableInfo, error) {
|
||||||
}
|
}
|
||||||
return t, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -73,3 +73,57 @@ func TestEmptyCompile(t *testing.T) {
|
||||||
t.Fatal(errors.New("expecting an error"))
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cespare/xxhash/v2"
|
||||||
"github.com/dosco/super-graph/util"
|
"github.com/dosco/super-graph/util"
|
||||||
"github.com/gobuffalo/flect"
|
"github.com/gobuffalo/flect"
|
||||||
)
|
)
|
||||||
|
@ -30,6 +31,7 @@ type Column struct {
|
||||||
type Select struct {
|
type Select struct {
|
||||||
ID uint16
|
ID uint16
|
||||||
ParentID uint16
|
ParentID uint16
|
||||||
|
RelID uint64
|
||||||
Args map[string]*Node
|
Args map[string]*Node
|
||||||
AsList bool
|
AsList bool
|
||||||
Table string
|
Table string
|
||||||
|
@ -255,6 +257,7 @@ func (com *Compiler) compileQuery(op *Operation) (*Query, error) {
|
||||||
|
|
||||||
selects := make([]Select, 0, 5)
|
selects := make([]Select, 0, 5)
|
||||||
st := util.NewStack()
|
st := util.NewStack()
|
||||||
|
h := xxhash.New()
|
||||||
|
|
||||||
if len(op.Fields) == 0 {
|
if len(op.Fields) == 0 {
|
||||||
return nil, errors.New("empty query")
|
return nil, errors.New("empty query")
|
||||||
|
@ -294,6 +297,7 @@ func (com *Compiler) compileQuery(op *Operation) (*Query, error) {
|
||||||
if s.ID != 0 {
|
if s.ID != 0 {
|
||||||
p := &selects[s.ParentID]
|
p := &selects[s.ParentID]
|
||||||
p.Children = append(p.Children, s.ID)
|
p.Children = append(p.Children, s.ID)
|
||||||
|
s.RelID = relID(h, tn, p.Table)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fn == tn {
|
if fn == tn {
|
||||||
|
@ -875,3 +879,11 @@ func buildPath(a []string) string {
|
||||||
}
|
}
|
||||||
return b.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
|
||||||
|
}
|
||||||
|
|
23
serv/core.go
23
serv/core.go
|
@ -34,8 +34,6 @@ type coreContext struct {
|
||||||
func (c *coreContext) handleReq(w io.Writer, req *http.Request) error {
|
func (c *coreContext) handleReq(w io.Writer, req *http.Request) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
//cacheEnabled := (conf.EnableTracing == false)
|
|
||||||
|
|
||||||
qc, err := qcompile.CompileQuery(c.req.Query)
|
qc, err := qcompile.CompileQuery(c.req.Query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -130,17 +128,12 @@ func (c *coreContext) handleReq(w io.Writer, req *http.Request) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if cacheEnabled {
|
|
||||||
// if err = cache.Set(key, []byte(finalSQL)); err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return c.render(w, ob.Bytes())
|
return c.render(w, ob.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *coreContext) resolveSQL(qc *qcode.QCode, vars variables) (
|
func (c *coreContext) resolveSQL(qc *qcode.QCode, vars variables) (
|
||||||
[]byte, uint32, error) {
|
[]byte, uint32, error) {
|
||||||
|
|
||||||
// var entry []byte
|
// var entry []byte
|
||||||
// var key string
|
// var key string
|
||||||
|
|
||||||
|
@ -180,13 +173,23 @@ func (c *coreContext) resolveSQL(qc *qcode.QCode, vars variables) (
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
finalSQL := sqlStmt.String()
|
finalSQL := sqlStmt.Bytes()
|
||||||
|
|
||||||
if conf.DebugLevel > 0 {
|
if conf.DebugLevel > 0 {
|
||||||
fmt.Println(finalSQL)
|
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
|
var root json.RawMessage
|
||||||
_, err = db.Query(pg.Scan(&root), finalSQL)
|
_, err = db.Query(pg.Scan(&root), finalSQL)
|
||||||
|
|
|
@ -45,7 +45,12 @@ func initRemotes(t configTable) {
|
||||||
|
|
||||||
// register a relationship between the remote data
|
// register a relationship between the remote data
|
||||||
// and the database table
|
// 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{
|
val := &psql.DBRel{
|
||||||
Type: psql.RelRemote,
|
Type: psql.RelRemote,
|
||||||
Col1: idcol,
|
Col1: idcol,
|
||||||
|
|
Loading…
Reference in New Issue