Compare commits

...

3 Commits

17 changed files with 214 additions and 175 deletions

View File

@ -12,8 +12,7 @@ FROM golang:1.14-alpine as go-build
RUN apk update && \ RUN apk update && \
apk add --no-cache make && \ apk add --no-cache make && \
apk add --no-cache git && \ apk add --no-cache git && \
apk add --no-cache jq && \ apk add --no-cache jq
apk add --no-cache upx=3.95-r2
RUN GO111MODULE=off go get -u github.com/rafaelsq/wtc RUN GO111MODULE=off go get -u github.com/rafaelsq/wtc

View File

@ -49,6 +49,7 @@ import (
"crypto/sha256" "crypto/sha256"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"hash/maphash"
_log "log" _log "log"
"os" "os"
@ -83,10 +84,11 @@ type SuperGraph struct {
schema *psql.DBSchema schema *psql.DBSchema
allowList *allow.List allowList *allow.List
encKey [32]byte encKey [32]byte
prepared map[string]*preparedItem hashSeed maphash.Seed
queries map[uint64]query
roles map[string]*Role roles map[string]*Role
getRole *sql.Stmt getRole *sql.Stmt
rmap map[uint64]*resolvFn rmap map[uint64]resolvFn
abacEnabled bool abacEnabled bool
anonExists bool anonExists bool
qc *qcode.Compiler qc *qcode.Compiler
@ -111,6 +113,7 @@ func newSuperGraph(conf *Config, db *sql.DB, dbinfo *psql.DBInfo) (*SuperGraph,
db: db, db: db,
dbinfo: dbinfo, dbinfo: dbinfo,
log: _log.New(os.Stdout, "", 0), log: _log.New(os.Stdout, "", 0),
hashSeed: maphash.MakeSeed(),
} }
if err := sg.initConfig(); err != nil { if err := sg.initConfig(); err != nil {

41
core/bench.11 Normal file
View File

@ -0,0 +1,41 @@
INF roles_query not defined: attribute based access control disabled
all expectations were already fulfilled, call to Query 'SELECT jsonb_build_object('users', "__sj_0"."json", 'products', "__sj_1"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_1"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "__sj_2"."json" AS "customers", "__sj_3"."json" AS "user" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('20') :: integer) AS "products_1" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_3".*) AS "json"FROM (SELECT "users_3"."full_name" AS "full_name", "users_3"."phone" AS "phone", "users_3"."email" AS "email" FROM (SELECT "users"."full_name", "users"."phone", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_1"."user_id"))) LIMIT ('1') :: integer) AS "users_3") AS "__sr_3") AS "__sj_3" ON ('true') LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_2"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_2".*) AS "json"FROM (SELECT "customers_2"."id" AS "id", "customers_2"."email" AS "email" FROM (SELECT "customers"."id", "customers"."email" FROM "customers" LEFT OUTER JOIN "purchases" ON (("purchases"."product_id") = ("products_1"."id")) WHERE ((("customers"."id") = ("purchases"."customer_id"))) LIMIT ('20') :: integer) AS "customers_2") AS "__sr_2") AS "__sj_2") AS "__sj_2" ON ('true')) AS "__sr_1") AS "__sj_1") AS "__sj_1", (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."id" AS "id", "users_0"."name" AS "name" FROM (SELECT "users"."id" FROM "users" GROUP BY "users"."id" LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0"' with args [] was not expected
goos: darwin
goarch: amd64
pkg: github.com/dosco/super-graph/core
BenchmarkGraphQL-16 INF roles_query not defined: attribute based access control disabled
all expectations were already fulfilled, call to Query 'SELECT jsonb_build_object('users', "__sj_0"."json", 'products', "__sj_1"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_1"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "__sj_2"."json" AS "customers", "__sj_3"."json" AS "user" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('20') :: integer) AS "products_1" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_3".*) AS "json"FROM (SELECT "users_3"."full_name" AS "full_name", "users_3"."phone" AS "phone", "users_3"."email" AS "email" FROM (SELECT "users"."full_name", "users"."phone", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_1"."user_id"))) LIMIT ('1') :: integer) AS "users_3") AS "__sr_3") AS "__sj_3" ON ('true') LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_2"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_2".*) AS "json"FROM (SELECT "customers_2"."id" AS "id", "customers_2"."email" AS "email" FROM (SELECT "customers"."id", "customers"."email" FROM "customers" LEFT OUTER JOIN "purchases" ON (("purchases"."product_id") = ("products_1"."id")) WHERE ((("customers"."id") = ("purchases"."customer_id"))) LIMIT ('20') :: integer) AS "customers_2") AS "__sr_2") AS "__sj_2") AS "__sj_2" ON ('true')) AS "__sr_1") AS "__sj_1") AS "__sj_1", (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."id" AS "id", "users_0"."name" AS "name" FROM (SELECT "users"."id" FROM "users" GROUP BY "users"."id" LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0"' with args [] was not expected
INF roles_query not defined: attribute based access control disabled
all expectations were already fulfilled, call to Query 'SELECT jsonb_build_object('users', "__sj_0"."json", 'products', "__sj_1"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_1"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "__sj_2"."json" AS "customers", "__sj_3"."json" AS "user" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('20') :: integer) AS "products_1" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_3".*) AS "json"FROM (SELECT "users_3"."full_name" AS "full_name", "users_3"."phone" AS "phone", "users_3"."email" AS "email" FROM (SELECT "users"."full_name", "users"."phone", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_1"."user_id"))) LIMIT ('1') :: integer) AS "users_3") AS "__sr_3") AS "__sj_3" ON ('true') LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_2"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_2".*) AS "json"FROM (SELECT "customers_2"."id" AS "id", "customers_2"."email" AS "email" FROM (SELECT "customers"."id", "customers"."email" FROM "customers" LEFT OUTER JOIN "purchases" ON (("purchases"."product_id") = ("products_1"."id")) WHERE ((("customers"."id") = ("purchases"."customer_id"))) LIMIT ('20') :: integer) AS "customers_2") AS "__sr_2") AS "__sj_2") AS "__sj_2" ON ('true')) AS "__sr_1") AS "__sj_1") AS "__sj_1", (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."id" AS "id", "users_0"."name" AS "name" FROM (SELECT "users"."id" FROM "users" GROUP BY "users"."id" LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0"' with args [] was not expected
INF roles_query not defined: attribute based access control disabled
all expectations were already fulfilled, call to Query 'SELECT jsonb_build_object('users', "__sj_0"."json", 'products', "__sj_1"."json") as "__root" FROM (SELECT coalesce(jsonb_agg("__sj_1"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_1".*) AS "json"FROM (SELECT "products_1"."id" AS "id", "products_1"."name" AS "name", "__sj_2"."json" AS "customers", "__sj_3"."json" AS "user" FROM (SELECT "products"."id", "products"."name", "products"."user_id" FROM "products" LIMIT ('20') :: integer) AS "products_1" LEFT OUTER JOIN LATERAL (SELECT to_jsonb("__sr_3".*) AS "json"FROM (SELECT "users_3"."full_name" AS "full_name", "users_3"."phone" AS "phone", "users_3"."email" AS "email" FROM (SELECT "users"."full_name", "users"."phone", "users"."email" FROM "users" WHERE ((("users"."id") = ("products_1"."user_id"))) LIMIT ('1') :: integer) AS "users_3") AS "__sr_3") AS "__sj_3" ON ('true') LEFT OUTER JOIN LATERAL (SELECT coalesce(jsonb_agg("__sj_2"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_2".*) AS "json"FROM (SELECT "customers_2"."id" AS "id", "customers_2"."email" AS "email" FROM (SELECT "customers"."id", "customers"."email" FROM "customers" LEFT OUTER JOIN "purchases" ON (("purchases"."product_id") = ("products_1"."id")) WHERE ((("customers"."id") = ("purchases"."customer_id"))) LIMIT ('20') :: integer) AS "customers_2") AS "__sr_2") AS "__sj_2") AS "__sj_2" ON ('true')) AS "__sr_1") AS "__sj_1") AS "__sj_1", (SELECT coalesce(jsonb_agg("__sj_0"."json"), '[]') as "json" FROM (SELECT to_jsonb("__sr_0".*) AS "json"FROM (SELECT "users_0"."id" AS "id", "users_0"."name" AS "name" FROM (SELECT "users"."id" FROM "users" GROUP BY "users"."id" LIMIT ('20') :: integer) AS "users_0") AS "__sr_0") AS "__sj_0") AS "__sj_0"' with args [] was not expected
105048 10398 ns/op 18342 B/op 55 allocs/op
PASS
ok github.com/dosco/super-graph/core 1.328s
PASS
ok github.com/dosco/super-graph/core/internal/allow 0.088s
? github.com/dosco/super-graph/core/internal/crypto [no test files]
? github.com/dosco/super-graph/core/internal/integration_tests [no test files]
PASS
ok github.com/dosco/super-graph/core/internal/integration_tests/cockroachdb 0.121s
PASS
ok github.com/dosco/super-graph/core/internal/integration_tests/postgresql 0.118s
goos: darwin
goarch: amd64
pkg: github.com/dosco/super-graph/core/internal/psql
BenchmarkCompile-16 79845 14428 ns/op 4584 B/op 39 allocs/op
BenchmarkCompileParallel-16 326205 3918 ns/op 4633 B/op 39 allocs/op
PASS
ok github.com/dosco/super-graph/core/internal/psql 2.696s
goos: darwin
goarch: amd64
pkg: github.com/dosco/super-graph/core/internal/qcode
BenchmarkQCompile-16 146953 8049 ns/op 3756 B/op 28 allocs/op
BenchmarkQCompileP-16 475936 2447 ns/op 3790 B/op 28 allocs/op
BenchmarkParse-16 140811 8163 ns/op 3902 B/op 18 allocs/op
BenchmarkParseP-16 571345 2041 ns/op 3903 B/op 18 allocs/op
BenchmarkSchemaParse-16 230715 5012 ns/op 3968 B/op 57 allocs/op
BenchmarkSchemaParseP-16 802426 1565 ns/op 3968 B/op 57 allocs/op
PASS
ok github.com/dosco/super-graph/core/internal/qcode 8.427s
? github.com/dosco/super-graph/core/internal/util [no test files]

View File

@ -5,6 +5,7 @@ import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"hash/maphash"
"time" "time"
"github.com/dosco/super-graph/core/internal/psql" "github.com/dosco/super-graph/core/internal/psql"
@ -165,32 +166,45 @@ func (c *scontext) resolvePreparedSQL() ([]byte, *stmt, error) {
} else { } else {
role = c.role role = c.role
} }
c.res.role = role c.res.role = role
ps, ok := c.sg.prepared[stmtHash(c.res.name, role)] h := maphash.Hash{}
h.SetSeed(c.sg.hashSeed)
q, ok := c.sg.queries[queryID(&h, c.res.name, role)]
if !ok { if !ok {
return nil, nil, errNotFound return nil, nil, errNotFound
} }
c.res.sql = ps.st.sql
if q.sd == nil {
q.Do(func() { c.sg.prepare(&q, role) })
if q.err != nil {
return nil, nil, err
}
}
c.res.sql = q.st.sql
var root []byte var root []byte
var row *sql.Row var row *sql.Row
varsList, err := c.argList(ps.st.md) varsList, err := c.argList(q.st.md)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
fmt.Println(">>", varsList)
if useTx { if useTx {
row = tx.Stmt(ps.sd).QueryRow(varsList...) row = tx.Stmt(q.sd).QueryRow(varsList...)
} else { } else {
row = ps.sd.QueryRow(varsList...) row = q.sd.QueryRow(varsList...)
} }
if ps.roleArg { if q.roleArg {
err = row.Scan(&role, &root) err = row.Scan(&role, &root)
} else { } else {
err = row.Scan(&root) err = row.Scan(&root)
@ -204,15 +218,15 @@ func (c *scontext) resolvePreparedSQL() ([]byte, *stmt, error) {
if useTx { if useTx {
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return nil, nil, err return nil, nil, q.err
} }
} }
if root, err = c.sg.encryptCursor(ps.st.qc, root); err != nil { if root, err = c.sg.encryptCursor(q.st.qc, root); err != nil {
return nil, nil, err return nil, nil, err
} }
return root, &ps.st, nil return root, &q.st, nil
} }
func (c *scontext) resolveSQL() ([]byte, *stmt, error) { func (c *scontext) resolveSQL() ([]byte, *stmt, error) {

View File

@ -2,120 +2,94 @@ package core
import ( import (
"bytes" "bytes"
"context"
"crypto/sha256"
"database/sql" "database/sql"
"encoding/hex"
"fmt" "fmt"
"hash/maphash"
"io" "io"
"strings" "strings"
"sync"
"github.com/dosco/super-graph/core/internal/allow" "github.com/dosco/super-graph/core/internal/allow"
"github.com/dosco/super-graph/core/internal/qcode" "github.com/dosco/super-graph/core/internal/qcode"
) )
type preparedItem struct { type query struct {
sync.Once
sd *sql.Stmt sd *sql.Stmt
ai allow.Item
qt qcode.QType
err error
st stmt st stmt
roleArg bool roleArg bool
} }
func (sg *SuperGraph) initPrepared() error { func (sg *SuperGraph) prepare(q *query, role string) {
ct := context.Background() var stmts []stmt
var err error
qb := []byte(q.ai.Query)
switch q.qt {
case qcode.QTQuery:
if sg.abacEnabled {
stmts, err = sg.buildMultiStmt(qb, q.ai.Vars)
} else {
stmts, err = sg.buildRoleStmt(qb, q.ai.Vars, role)
}
case qcode.QTMutation:
stmts, err = sg.buildRoleStmt(qb, q.ai.Vars, role)
}
if err != nil {
sg.log.Printf("WRN %s %s: %v", q.qt, q.ai.Name, err)
}
q.st = stmts[0]
q.roleArg = len(stmts) > 1
q.sd, err = sg.db.Prepare(q.st.sql)
if err != nil {
q.err = fmt.Errorf("prepare failed: %v: %s", err, q.st.sql)
}
}
func (sg *SuperGraph) initPrepared() error {
if sg.allowList.IsPersist() { if sg.allowList.IsPersist() {
return nil return nil
} }
sg.prepared = make(map[string]*preparedItem)
tx, err := sg.db.BeginTx(ct, nil) if err := sg.prepareRoleStmt(); err != nil {
if err != nil { return fmt.Errorf("role query: %w", err)
return err
}
defer tx.Rollback() //nolint: errcheck
if err = sg.prepareRoleStmt(tx); err != nil {
return fmt.Errorf("prepareRoleStmt: %w", err)
} }
if err := tx.Commit(); err != nil { sg.queries = make(map[uint64]query)
return err
}
success := 0
list, err := sg.allowList.Load() list, err := sg.allowList.Load()
if err != nil { if err != nil {
return err return err
} }
h := maphash.Hash{}
h.SetSeed(sg.hashSeed)
for _, v := range list { for _, v := range list {
if len(v.Query) == 0 { if len(v.Query) == 0 {
continue continue
} }
qt := qcode.GetQType(v.Query)
err := sg.prepareStmt(v)
if err != nil {
return err
} else {
success++
}
}
sg.log.Printf("INF allow list: prepared %d / %d queries", success, len(list))
return nil
}
func (sg *SuperGraph) prepareStmt(item allow.Item) error {
query := item.Query
qb := []byte(query)
vars := item.Vars
qt := qcode.GetQType(query)
ct := context.Background()
switch qt { switch qt {
case qcode.QTQuery: case qcode.QTQuery:
var stmts1 []stmt sg.queries[queryID(&h, v.Name, "user")] = query{ai: v, qt: qt}
var err error
if sg.abacEnabled {
stmts1, err = sg.buildMultiStmt(qb, vars)
} else {
stmts1, err = sg.buildRoleStmt(qb, vars, "user")
}
if err == nil {
if err = sg.prepare(ct, stmts1, stmtHash(item.Name, "user")); err != nil {
return err
}
} else {
sg.log.Printf("WRN query %s: %v", item.Name, err)
}
if sg.anonExists { if sg.anonExists {
stmts2, err := sg.buildRoleStmt(qb, vars, "anon") sg.queries[queryID(&h, v.Name, "anon")] = query{ai: v, qt: qt}
if err == nil {
if err = sg.prepare(ct, stmts2, stmtHash(item.Name, "anon")); err != nil {
return err
}
} else {
sg.log.Printf("WRN query %s: %v", item.Name, err)
}
} }
case qcode.QTMutation: case qcode.QTMutation:
for _, role := range sg.conf.Roles { for _, role := range sg.conf.Roles {
stmts, err := sg.buildRoleStmt(qb, vars, role.Name) sg.queries[queryID(&h, v.Name, role.Name)] = query{ai: v, qt: qt}
if err == nil {
if err = sg.prepare(ct, stmts, stmtHash(item.Name, role.Name)); err != nil {
return err
}
} else {
sg.log.Printf("WRN mutation %s: %v", item.Name, err)
} }
} }
} }
@ -123,22 +97,8 @@ func (sg *SuperGraph) prepareStmt(item allow.Item) error {
return nil return nil
} }
func (sg *SuperGraph) prepare(ct context.Context, st []stmt, key string) error {
sd, err := sg.db.PrepareContext(ct, st[0].sql)
if err != nil {
return fmt.Errorf("prepare failed: %v: %s", err, st[0].sql)
}
sg.prepared[key] = &preparedItem{
sd: sd,
st: st[0],
roleArg: len(st) > 1,
}
return nil
}
// nolint: errcheck // nolint: errcheck
func (sg *SuperGraph) prepareRoleStmt(tx *sql.Tx) error { func (sg *SuperGraph) prepareRoleStmt() error {
var err error var err error
if !sg.abacEnabled { if !sg.abacEnabled {
@ -169,7 +129,7 @@ func (sg *SuperGraph) prepareRoleStmt(tx *sql.Tx) error {
io.WriteString(w, `) AS "_sg_auth_roles_query" LIMIT 1) `) io.WriteString(w, `) AS "_sg_auth_roles_query" LIMIT 1) `)
io.WriteString(w, `ELSE 'anon' END) FROM (VALUES (1)) AS "_sg_auth_filler" LIMIT 1; `) io.WriteString(w, `ELSE 'anon' END) FROM (VALUES (1)) AS "_sg_auth_filler" LIMIT 1; `)
sg.getRole, err = tx.Prepare(w.String()) sg.getRole, err = sg.db.Prepare(w.String())
if err != nil { if err != nil {
return err return err
} }
@ -200,9 +160,11 @@ func (sg *SuperGraph) initAllowList() error {
} }
// nolint: errcheck // nolint: errcheck
func stmtHash(name string, role string) string { func queryID(h *maphash.Hash, name string, role string) uint64 {
h := sha256.New() h.WriteString(name)
io.WriteString(h, strings.ToLower(name)) h.WriteString(role)
io.WriteString(h, role) v := h.Sum64()
return hex.EncodeToString(h.Sum(nil)) h.Reset()
return v
} }

View File

@ -4,10 +4,10 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"hash/maphash"
"net/http" "net/http"
"sync" "sync"
"github.com/cespare/xxhash/v2"
"github.com/dosco/super-graph/core/internal/qcode" "github.com/dosco/super-graph/core/internal/qcode"
"github.com/dosco/super-graph/jsn" "github.com/dosco/super-graph/jsn"
) )
@ -16,12 +16,13 @@ func (sg *SuperGraph) execRemoteJoin(st *stmt, data []byte, hdr http.Header) ([]
var err error var err error
sel := st.qc.Selects sel := st.qc.Selects
h := xxhash.New() h := maphash.Hash{}
h.SetSeed(sg.hashSeed)
// fetch the field name used within the db response json // fetch the field name used within the db response json
// that are used to mark insertion points and the mapping between // that are used to mark insertion points and the mapping between
// those field names and their select objects // those field names and their select objects
fids, sfmap := sg.parentFieldIds(h, sel, st.md.Skipped) fids, sfmap := sg.parentFieldIds(&h, sel, st.md.Skipped)
// fetch the field values of the marked insertion points // fetch the field values of the marked insertion points
// these values contain the id to be used with fetching remote data // these values contain the id to be used with fetching remote data
@ -30,10 +31,10 @@ func (sg *SuperGraph) execRemoteJoin(st *stmt, data []byte, hdr http.Header) ([]
switch { switch {
case len(from) == 1: case len(from) == 1:
to, err = sg.resolveRemote(hdr, h, from[0], sel, sfmap) to, err = sg.resolveRemote(hdr, &h, from[0], sel, sfmap)
case len(from) > 1: case len(from) > 1:
to, err = sg.resolveRemotes(hdr, h, from, sel, sfmap) to, err = sg.resolveRemotes(hdr, &h, from, sel, sfmap)
default: default:
return nil, errors.New("something wrong no remote ids found in db response") return nil, errors.New("something wrong no remote ids found in db response")
@ -55,7 +56,7 @@ func (sg *SuperGraph) execRemoteJoin(st *stmt, data []byte, hdr http.Header) ([]
func (sg *SuperGraph) resolveRemote( func (sg *SuperGraph) resolveRemote(
hdr http.Header, hdr http.Header,
h *xxhash.Digest, h *maphash.Hash,
field jsn.Field, field jsn.Field,
sel []qcode.Select, sel []qcode.Select,
sfmap map[uint64]*qcode.Select) ([]jsn.Field, error) { sfmap map[uint64]*qcode.Select) ([]jsn.Field, error) {
@ -66,7 +67,8 @@ func (sg *SuperGraph) resolveRemote(
to := toA[:1] to := toA[:1]
// use the json key to find the related Select object // use the json key to find the related Select object
k1 := xxhash.Sum64(field.Key) h.Write(field.Key)
k1 := h.Sum64()
s, ok := sfmap[k1] s, ok := sfmap[k1]
if !ok { if !ok {
@ -117,7 +119,7 @@ func (sg *SuperGraph) resolveRemote(
func (sg *SuperGraph) resolveRemotes( func (sg *SuperGraph) resolveRemotes(
hdr http.Header, hdr http.Header,
h *xxhash.Digest, h *maphash.Hash,
from []jsn.Field, from []jsn.Field,
sel []qcode.Select, sel []qcode.Select,
sfmap map[uint64]*qcode.Select) ([]jsn.Field, error) { sfmap map[uint64]*qcode.Select) ([]jsn.Field, error) {
@ -134,7 +136,8 @@ func (sg *SuperGraph) resolveRemotes(
for i, id := range from { for i, id := range from {
// use the json key to find the related Select object // use the json key to find the related Select object
k1 := xxhash.Sum64(id.Key) h.Write(id.Key)
k1 := h.Sum64()
s, ok := sfmap[k1] s, ok := sfmap[k1]
if !ok { if !ok {
@ -192,7 +195,7 @@ func (sg *SuperGraph) resolveRemotes(
return to, cerr return to, cerr
} }
func (sg *SuperGraph) parentFieldIds(h *xxhash.Digest, sel []qcode.Select, skipped uint32) ( func (sg *SuperGraph) parentFieldIds(h *maphash.Hash, sel []qcode.Select, skipped uint32) (
[][]byte, [][]byte,
map[uint64]*qcode.Select) { map[uint64]*qcode.Select) {
@ -227,8 +230,8 @@ func (sg *SuperGraph) parentFieldIds(h *xxhash.Digest, sel []qcode.Select, skipp
fm[n] = r.IDField fm[n] = r.IDField
n++ n++
k := xxhash.Sum64(r.IDField) h.Write(r.IDField)
sm[k] = s sm[h.Sum64()] = s
} }
} }

View File

@ -2,11 +2,11 @@ package core
import ( import (
"fmt" "fmt"
"hash/maphash"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strings" "strings"
"github.com/cespare/xxhash/v2"
"github.com/dosco/super-graph/core/internal/psql" "github.com/dosco/super-graph/core/internal/psql"
"github.com/dosco/super-graph/jsn" "github.com/dosco/super-graph/jsn"
) )
@ -19,7 +19,7 @@ type resolvFn struct {
func (sg *SuperGraph) initResolvers() error { func (sg *SuperGraph) initResolvers() error {
var err error var err error
sg.rmap = make(map[uint64]*resolvFn) sg.rmap = make(map[uint64]resolvFn)
for _, t := range sg.conf.Tables { for _, t := range sg.conf.Tables {
err = sg.initRemotes(t) err = sg.initRemotes(t)
@ -36,7 +36,8 @@ func (sg *SuperGraph) initResolvers() error {
} }
func (sg *SuperGraph) initRemotes(t Table) error { func (sg *SuperGraph) initRemotes(t Table) error {
h := xxhash.New() h := maphash.Hash{}
h.SetSeed(sg.hashSeed)
for _, r := range t.Remotes { for _, r := range t.Remotes {
// defines the table column to be used as an id in the // defines the table column to be used as an id in the
@ -75,17 +76,18 @@ func (sg *SuperGraph) initRemotes(t Table) error {
path = append(path, []byte(p)) path = append(path, []byte(p))
} }
rf := &resolvFn{ rf := resolvFn{
IDField: []byte(idk), IDField: []byte(idk),
Path: path, Path: path,
Fn: fn, Fn: fn,
} }
// index resolver obj by parent and child names // index resolver obj by parent and child names
sg.rmap[mkkey(h, r.Name, t.Name)] = rf sg.rmap[mkkey(&h, r.Name, t.Name)] = rf
// index resolver obj by IDField // index resolver obj by IDField
sg.rmap[xxhash.Sum64(rf.IDField)] = rf h.Write(rf.IDField)
sg.rmap[h.Sum64()] = rf
} }
return nil return nil

View File

@ -1,11 +1,9 @@
package core package core
import ( import "hash/maphash"
"github.com/cespare/xxhash/v2"
)
// nolint: errcheck // nolint: errcheck
func mkkey(h *xxhash.Digest, k1 string, k2 string) uint64 { func mkkey(h *maphash.Hash, k1 string, k2 string) uint64 {
h.WriteString(k1) h.WriteString(k1)
h.WriteString(k2) h.WriteString(k2)
v := h.Sum64() v := h.Sum64()

View File

@ -36,8 +36,8 @@ module.exports = {
position: "left", position: "left",
}, },
{ {
label: "Art Compute", label: "AbtCode",
href: "https://artcompute.com/s/super-graph", href: "https://abtcode.com/s/super-graph",
position: "left", position: "left",
}, },
], ],

3
go.mod
View File

@ -12,13 +12,11 @@ require (
github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3 github.com/adjust/gorails v0.0.0-20171013043634-2786ed0c03d3
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
github.com/brianvoe/gofakeit/v5 v5.2.0 github.com/brianvoe/gofakeit/v5 v5.2.0
github.com/cespare/xxhash/v2 v2.1.1
github.com/chirino/graphql v0.0.0-20200430165312-293648399b1a github.com/chirino/graphql v0.0.0-20200430165312-293648399b1a
github.com/daaku/go.zipexe v1.0.1 // indirect github.com/daaku/go.zipexe v1.0.1 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dlclark/regexp2 v1.2.0 // indirect github.com/dlclark/regexp2 v1.2.0 // indirect
github.com/dop251/goja v0.0.0-20200424152103-d0b8fda54cd0 github.com/dop251/goja v0.0.0-20200424152103-d0b8fda54cd0
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 // indirect
github.com/fsnotify/fsnotify v1.4.9 github.com/fsnotify/fsnotify v1.4.9
github.com/garyburd/redigo v1.6.0 github.com/garyburd/redigo v1.6.0
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
@ -30,7 +28,6 @@ require (
github.com/openzipkin/zipkin-go v0.2.2 github.com/openzipkin/zipkin-go v0.2.2
github.com/pelletier/go-toml v1.7.0 // indirect github.com/pelletier/go-toml v1.7.0 // indirect
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/common v0.4.0
github.com/rs/cors v1.7.0 github.com/rs/cors v1.7.0
github.com/spf13/afero v1.2.2 // indirect github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cast v1.3.1 // indirect

4
go.sum
View File

@ -55,8 +55,6 @@ github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chirino/graphql v0.0.0-20200430165312-293648399b1a h1:WVu7r2vwlrBVmunbSSU+9/3M3AgsQyhE49CKDjHiFq4= github.com/chirino/graphql v0.0.0-20200430165312-293648399b1a h1:WVu7r2vwlrBVmunbSSU+9/3M3AgsQyhE49CKDjHiFq4=
github.com/chirino/graphql v0.0.0-20200430165312-293648399b1a/go.mod h1:wQjjxFMFyMlsWh4Z3nMuHQtevD4Ul9UVQSnz1JOLuP8= github.com/chirino/graphql v0.0.0-20200430165312-293648399b1a/go.mod h1:wQjjxFMFyMlsWh4Z3nMuHQtevD4Ul9UVQSnz1JOLuP8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@ -87,8 +85,6 @@ github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dop251/goja v0.0.0-20200424152103-d0b8fda54cd0 h1:EfFAcaAwGai/wlDCWwIObHBm3T2C2CCPX/SaS0fpOJ4= github.com/dop251/goja v0.0.0-20200424152103-d0b8fda54cd0 h1:EfFAcaAwGai/wlDCWwIObHBm3T2C2CCPX/SaS0fpOJ4=
github.com/dop251/goja v0.0.0-20200424152103-d0b8fda54cd0/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= github.com/dop251/goja v0.0.0-20200424152103-d0b8fda54cd0/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 h1:NgO45/5mBLRVfiXerEFzH6ikcZ7DNRPS639xFg3ENzU=
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=

View File

@ -82,8 +82,6 @@ func graphQLFunc(sg *core.SuperGraph, query string, data interface{}, opt map[st
if v, ok := opt["user_id"]; ok && len(v) != 0 { if v, ok := opt["user_id"]; ok && len(v) != 0 {
ct = context.WithValue(ct, core.UserIDKey, v) ct = context.WithValue(ct, core.UserIDKey, v)
} else {
ct = context.WithValue(ct, core.UserIDKey, "-1")
} }
// var role string // var role string

View File

@ -6,9 +6,11 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"sort"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
@ -105,39 +107,40 @@ func (defaultMigratorFS) Glob(pattern string) ([]string, error) {
func FindMigrationsEx(path string, fs MigratorFS) ([]string, error) { func FindMigrationsEx(path string, fs MigratorFS) ([]string, error) {
path = strings.TrimRight(path, string(filepath.Separator)) path = strings.TrimRight(path, string(filepath.Separator))
fileInfos, err := fs.ReadDir(path) files, err := ioutil.ReadDir(path)
if err != nil { if err != nil {
return nil, err log.Fatal(err)
} }
paths := make([]string, 0, len(fileInfos)) fm := make(map[int]string, len(files))
for _, fi := range fileInfos { keys := make([]int, 0, len(files))
for _, fi := range files {
if fi.IsDir() { if fi.IsDir() {
continue continue
} }
matches := migrationPattern.FindStringSubmatch(fi.Name()) matches := migrationPattern.FindStringSubmatch(fi.Name())
if len(matches) != 2 { if len(matches) != 2 {
continue continue
} }
n, err := strconv.ParseInt(matches[1], 10, 32) n, err := strconv.Atoi(matches[1])
if err != nil { if err != nil {
// The regexp already validated that the prefix is all digits so this *should* never fail // The regexp already validated that the prefix is all digits so this *should* never fail
return nil, err return nil, err
} }
mcount := len(paths) fm[n] = filepath.Join(path, fi.Name())
keys = append(keys, n)
if n < int64(mcount) {
return nil, fmt.Errorf("Duplicate migration %d", n)
} }
if int64(mcount) < n { sort.Ints(keys)
return nil, fmt.Errorf("Missing migration %d", mcount)
}
paths = append(paths, filepath.Join(path, fi.Name())) paths := make([]string, 0, len(keys))
for _, k := range keys {
paths = append(paths, fm[k])
} }
return paths, nil return paths, nil

13
jsn/bench.1 Normal file
View File

@ -0,0 +1,13 @@
goos: darwin
goarch: amd64
pkg: github.com/dosco/super-graph/jsn
BenchmarkGet
BenchmarkGet-16 13898 85293 ns/op 3328 B/op 2 allocs/op
BenchmarkFilter
BenchmarkFilter-16 189328 6341 ns/op 448 B/op 1 allocs/op
BenchmarkStrip
BenchmarkStrip-16 219765 5543 ns/op 224 B/op 1 allocs/op
BenchmarkReplace
BenchmarkReplace-16 100899 12022 ns/op 416 B/op 1 allocs/op
PASS
ok github.com/dosco/super-graph/jsn 6.029s

View File

@ -2,17 +2,19 @@ package jsn
import ( import (
"bytes" "bytes"
"hash/maphash"
"github.com/cespare/xxhash/v2"
) )
// Filter function filters the JSON keeping only the provided keys and removing all others // Filter function filters the JSON keeping only the provided keys and removing all others
func Filter(w *bytes.Buffer, b []byte, keys []string) error { func Filter(w *bytes.Buffer, b []byte, keys []string) error {
var err error var err error
kmap := make(map[uint64]struct{}, len(keys)) kmap := make(map[uint64]struct{}, len(keys))
h := maphash.Hash{}
for i := range keys { for i := range keys {
kmap[xxhash.Sum64String(keys[i])] = struct{}{} h.WriteString(keys[i])
kmap[h.Sum64()] = struct{}{}
h.Reset()
} }
// is an list // is an list
@ -132,7 +134,11 @@ func Filter(w *bytes.Buffer, b []byte, keys []string) error {
cb := b[s:(e + 1)] cb := b[s:(e + 1)]
e = 0 e = 0
if _, ok := kmap[xxhash.Sum64(k)]; !ok { h.Write(k)
_, ok := kmap[h.Sum64()]
h.Reset()
if !ok {
continue continue
} }

View File

@ -1,7 +1,7 @@
package jsn package jsn
import ( import (
"github.com/cespare/xxhash/v2" "hash/maphash"
) )
const ( const (
@ -41,9 +41,12 @@ func Value(b []byte) []byte {
// Keys function fetches values for the provided keys // Keys function fetches values for the provided keys
func Get(b []byte, keys [][]byte) []Field { func Get(b []byte, keys [][]byte) []Field {
kmap := make(map[uint64]struct{}, len(keys)) kmap := make(map[uint64]struct{}, len(keys))
h := maphash.Hash{}
for i := range keys { for i := range keys {
kmap[xxhash.Sum64(keys[i])] = struct{}{} h.Write(keys[i])
kmap[h.Sum64()] = struct{}{}
h.Reset()
} }
res := make([]Field, 0, 20) res := make([]Field, 0, 20)
@ -141,7 +144,9 @@ func Get(b []byte, keys [][]byte) []Field {
} }
if e != 0 { if e != 0 {
_, ok := kmap[xxhash.Sum64(k)] h.Write(k)
_, ok := kmap[h.Sum64()]
h.Reset()
if ok { if ok {
res = append(res, Field{k, b[s:(e + 1)]}) res = append(res, Field{k, b[s:(e + 1)]})

View File

@ -3,8 +3,7 @@ package jsn
import ( import (
"bytes" "bytes"
"errors" "errors"
"hash/maphash"
"github.com/cespare/xxhash/v2"
) )
// Replace function replaces key-value pairs provided in the `from` argument with those in the `to` argument // Replace function replaces key-value pairs provided in the `from` argument with those in the `to` argument
@ -18,7 +17,7 @@ func Replace(w *bytes.Buffer, b []byte, from, to []Field) error {
return err return err
} }
h := xxhash.New() h := maphash.Hash{}
tmap := make(map[uint64]int, len(from)) tmap := make(map[uint64]int, len(from))
for i, f := range from { for i, f := range from {