Reduce realloc of maps and slices
This commit is contained in:
parent
b562e1603e
commit
8acc3ed08d
13
README.md
13
README.md
|
@ -19,13 +19,14 @@ I wanted a GraphQL server that just worked the second you deployed it without ha
|
||||||
And so after a lot of coffee and some avocado toasts Super Graph was born. An instant GraphQL API service that's high performance and easy to deploy. I hope you find it as useful as I do and there's a lot more coming so hit that :star: to stay in the loop.
|
And so after a lot of coffee and some avocado toasts Super Graph was born. An instant GraphQL API service that's high performance and easy to deploy. I hope you find it as useful as I do and there's a lot more coming so hit that :star: to stay in the loop.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Support for Rails database conventions
|
- Works with Rails database schemas
|
||||||
|
- Automatically learns schemas and relationships
|
||||||
- Belongs-To, One-To-Many and Many-To-Many table relationships
|
- Belongs-To, One-To-Many and Many-To-Many table relationships
|
||||||
- Devise, Warden encrypted and signed session cookies
|
- Full text search and Aggregations
|
||||||
- Redis, Memcache and Cookie session stores
|
- Rails Auth supported (Redis, Memcache, Cookie)
|
||||||
- JWT tokens supported from providers like Auth0
|
- JWT tokens supported (Auth0, etc)
|
||||||
- Generates highly optimized and fast Postgres SQL queries
|
- Highly optimized and fast Postgres SQL queries
|
||||||
- Customize through a simple config file
|
- Configure with a simple config file
|
||||||
- High performance GO codebase
|
- High performance GO codebase
|
||||||
- Tiny docker image and low memory requirements
|
- Tiny docker image and low memory requirements
|
||||||
|
|
||||||
|
|
25
psql/psql.go
25
psql/psql.go
|
@ -274,7 +274,7 @@ func (v *selectBlock) renderBaseSelect(w io.Writer, schema *DBSchema, childCols
|
||||||
isFil := v.sel.Where != nil
|
isFil := v.sel.Where != nil
|
||||||
isAgg := false
|
isAgg := false
|
||||||
|
|
||||||
searchVal := findArgVal(v.sel, "search")
|
_, isSearch := v.sel.Args["search"]
|
||||||
|
|
||||||
io.WriteString(w, " FROM (SELECT ")
|
io.WriteString(w, " FROM (SELECT ")
|
||||||
|
|
||||||
|
@ -284,15 +284,19 @@ func (v *selectBlock) renderBaseSelect(w io.Writer, schema *DBSchema, childCols
|
||||||
|
|
||||||
if !isRealCol {
|
if !isRealCol {
|
||||||
switch {
|
switch {
|
||||||
case searchVal != nil && cn == "search_rank":
|
case isSearch && cn == "search_rank":
|
||||||
cn = v.ti.TSVCol
|
cn = v.ti.TSVCol
|
||||||
fmt.Fprintf(w, `ts_rank("%s"."%s", to_tsquery('%s')) AS %s`,
|
arg := v.sel.Args["search"]
|
||||||
v.sel.Table, cn, searchVal.Val, col.Name)
|
|
||||||
|
|
||||||
case searchVal != nil && strings.HasPrefix(cn, "search_headline_"):
|
fmt.Fprintf(w, `ts_rank("%s"."%s", to_tsquery('%s')) AS %s`,
|
||||||
|
v.sel.Table, cn, arg.Val, col.Name)
|
||||||
|
|
||||||
|
case isSearch && strings.HasPrefix(cn, "search_headline_"):
|
||||||
cn = cn[16:]
|
cn = cn[16:]
|
||||||
|
arg := v.sel.Args["search"]
|
||||||
|
|
||||||
fmt.Fprintf(w, `ts_headline("%s"."%s", to_tsquery('%s')) AS %s`,
|
fmt.Fprintf(w, `ts_headline("%s"."%s", to_tsquery('%s')) AS %s`,
|
||||||
v.sel.Table, cn, searchVal.Val, col.Name)
|
v.sel.Table, cn, arg.Val, col.Name)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
pl := funcPrefixLen(cn)
|
pl := funcPrefixLen(cn)
|
||||||
|
@ -647,12 +651,3 @@ func funcPrefixLen(fn string) int {
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func findArgVal(sel *qcode.Select, name string) *qcode.Node {
|
|
||||||
for i := range sel.Args {
|
|
||||||
if sel.Args[i].Name == name {
|
|
||||||
return sel.Args[i].Val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import "regexp"
|
||||||
|
|
||||||
func NewVariables(varlist map[string]string) map[string]string {
|
func NewVariables(varlist map[string]string) map[string]string {
|
||||||
re := regexp.MustCompile(`(?mi)\$([a-zA-Z0-9_.]+)`)
|
re := regexp.MustCompile(`(?mi)\$([a-zA-Z0-9_.]+)`)
|
||||||
vars := make(map[string]string)
|
vars := make(map[string]string, len(varlist))
|
||||||
|
|
||||||
for k, v := range varlist {
|
for k, v := range varlist {
|
||||||
vars[k] = re.ReplaceAllString(v, `{{$1}}`)
|
vars[k] = re.ReplaceAllString(v, `{{$1}}`)
|
||||||
|
|
|
@ -63,9 +63,11 @@ type Operation struct {
|
||||||
Name string
|
Name string
|
||||||
Args []*Arg
|
Args []*Arg
|
||||||
Fields []*Field
|
Fields []*Field
|
||||||
|
FieldLen int16
|
||||||
}
|
}
|
||||||
|
|
||||||
type Field struct {
|
type Field struct {
|
||||||
|
ID int16
|
||||||
Name string
|
Name string
|
||||||
Alias string
|
Alias string
|
||||||
Args []*Arg
|
Args []*Arg
|
||||||
|
@ -200,10 +202,12 @@ func (p *Parser) parseOpByType(ty parserType) (*Operation, error) {
|
||||||
|
|
||||||
if p.peek(itemObjOpen) {
|
if p.peek(itemObjOpen) {
|
||||||
p.ignore()
|
p.ignore()
|
||||||
op.Fields, err = p.parseFields()
|
n := int16(0)
|
||||||
|
op.Fields, n, err = p.parseFields()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
op.FieldLen = n
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.peek(itemObjClose) {
|
if p.peek(itemObjClose) {
|
||||||
|
@ -233,9 +237,10 @@ func (p *Parser) parseOp() (*Operation, error) {
|
||||||
return nil, errors.New("unknown operation type")
|
return nil, errors.New("unknown operation type")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) parseFields() ([]*Field, error) {
|
func (p *Parser) parseFields() ([]*Field, int16, error) {
|
||||||
var roots []*Field
|
var roots []*Field
|
||||||
st := util.NewStack()
|
st := util.NewStack()
|
||||||
|
i := int16(0)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if p.peek(itemObjClose) {
|
if p.peek(itemObjClose) {
|
||||||
|
@ -248,14 +253,20 @@ func (p *Parser) parseFields() ([]*Field, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if i > 500 {
|
||||||
|
return nil, 0, errors.New("too many fields")
|
||||||
|
}
|
||||||
|
|
||||||
if p.peek(itemName) == false {
|
if p.peek(itemName) == false {
|
||||||
return nil, errors.New("expecting an alias or field name")
|
return nil, 0, errors.New("expecting an alias or field name")
|
||||||
}
|
}
|
||||||
|
|
||||||
field, err := p.parseField()
|
field, err := p.parseField()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
field.ID = i
|
||||||
|
i++
|
||||||
|
|
||||||
if st.Len() == 0 {
|
if st.Len() == 0 {
|
||||||
roots = append(roots, field)
|
roots = append(roots, field)
|
||||||
|
@ -264,7 +275,7 @@ func (p *Parser) parseFields() ([]*Field, error) {
|
||||||
intf := st.Peek()
|
intf := st.Peek()
|
||||||
parent, ok := intf.(*Field)
|
parent, ok := intf.(*Field)
|
||||||
if !ok || parent == nil {
|
if !ok || parent == nil {
|
||||||
return nil, fmt.Errorf("unexpected value encountered %v", intf)
|
return nil, 0, fmt.Errorf("unexpected value encountered %v", intf)
|
||||||
}
|
}
|
||||||
field.Parent = parent
|
field.Parent = parent
|
||||||
parent.Children = append(parent.Children, field)
|
parent.Children = append(parent.Children, field)
|
||||||
|
@ -276,7 +287,7 @@ func (p *Parser) parseFields() ([]*Field, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return roots, nil
|
return roots, i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) parseField() (*Field, error) {
|
func (p *Parser) parseField() (*Field, error) {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package qcode
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/dosco/super-graph/util"
|
"github.com/dosco/super-graph/util"
|
||||||
|
@ -25,7 +24,7 @@ type Column struct {
|
||||||
|
|
||||||
type Select struct {
|
type Select struct {
|
||||||
ID int16
|
ID int16
|
||||||
Args []*Arg
|
Args map[string]*Node
|
||||||
AsList bool
|
AsList bool
|
||||||
Table string
|
Table string
|
||||||
Singular string
|
Singular string
|
||||||
|
@ -181,7 +180,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type FilterMap map[string]*Exp
|
type FilterMap map[string]*Exp
|
||||||
type Blacklist *regexp.Regexp
|
type Blacklist map[string]struct{}
|
||||||
|
|
||||||
func CompileFilter(filter string) (*Exp, error) {
|
func CompileFilter(filter string) (*Exp, error) {
|
||||||
node, err := ParseArgValue(filter)
|
node, err := ParseArgValue(filter)
|
||||||
|
@ -194,7 +193,7 @@ func CompileFilter(filter string) (*Exp, error) {
|
||||||
|
|
||||||
type Compiler struct {
|
type Compiler struct {
|
||||||
fm FilterMap
|
fm FilterMap
|
||||||
bl *regexp.Regexp
|
bl Blacklist
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCompiler(fm FilterMap, bl Blacklist) *Compiler {
|
func NewCompiler(fm FilterMap, bl Blacklist) *Compiler {
|
||||||
|
@ -231,7 +230,7 @@ func (com *Compiler) compileQuery(op *Operation) (*Query, error) {
|
||||||
|
|
||||||
st := util.NewStack()
|
st := util.NewStack()
|
||||||
id := int16(0)
|
id := int16(0)
|
||||||
fmap := make(map[*Field]*Select)
|
fs := make([]*Select, op.FieldLen)
|
||||||
|
|
||||||
for i := range op.Fields {
|
for i := range op.Fields {
|
||||||
st.Push(op.Fields[i])
|
st.Push(op.Fields[i])
|
||||||
|
@ -249,11 +248,10 @@ func (com *Compiler) compileQuery(op *Operation) (*Query, error) {
|
||||||
return nil, fmt.Errorf("unexpected value poped out %v", intf)
|
return nil, fmt.Errorf("unexpected value poped out %v", intf)
|
||||||
}
|
}
|
||||||
|
|
||||||
if com.bl != nil && com.bl.MatchString(field.Name) {
|
fn := strings.ToLower(field.Name)
|
||||||
|
if _, ok := com.bl[fn]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fn := strings.ToLower(field.Name)
|
|
||||||
tn := flect.Pluralize(fn)
|
tn := flect.Pluralize(fn)
|
||||||
|
|
||||||
s := &Select{
|
s := &Select{
|
||||||
|
@ -282,7 +280,7 @@ func (com *Compiler) compileQuery(op *Operation) (*Query, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
id++
|
id++
|
||||||
fmap[field] = s
|
fs[field.ID] = s
|
||||||
|
|
||||||
err := com.compileArgs(s, field.Args)
|
err := com.compileArgs(s, field.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -293,7 +291,7 @@ func (com *Compiler) compileQuery(op *Operation) (*Query, error) {
|
||||||
f := field.Children[i]
|
f := field.Children[i]
|
||||||
fn := strings.ToLower(f.Name)
|
fn := strings.ToLower(f.Name)
|
||||||
|
|
||||||
if com.bl != nil && com.bl.MatchString(fn) {
|
if _, ok := com.bl[fn]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,10 +311,9 @@ func (com *Compiler) compileQuery(op *Operation) (*Query, error) {
|
||||||
|
|
||||||
if field.Parent == nil {
|
if field.Parent == nil {
|
||||||
selRoot = s
|
selRoot = s
|
||||||
} else if sp, ok := fmap[field.Parent]; ok {
|
|
||||||
sp.Joins = append(sp.Joins, s)
|
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("no select found for parent %#v", field.Parent)
|
sp := fs[field.Parent.ID]
|
||||||
|
sp.Joins = append(sp.Joins, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,14 +330,15 @@ func (com *Compiler) compileQuery(op *Operation) (*Query, error) {
|
||||||
|
|
||||||
func (com *Compiler) compileArgs(sel *Select, args []*Arg) error {
|
func (com *Compiler) compileArgs(sel *Select, args []*Arg) error {
|
||||||
var err error
|
var err error
|
||||||
ad := make(map[string]struct{})
|
|
||||||
|
sel.Args = make(map[string]*Node, len(args))
|
||||||
|
|
||||||
for i := range args {
|
for i := range args {
|
||||||
if args[i] == nil {
|
if args[i] == nil {
|
||||||
return fmt.Errorf("[Args] unexpected nil argument found")
|
return fmt.Errorf("[Args] unexpected nil argument found")
|
||||||
}
|
}
|
||||||
an := strings.ToLower(args[i].Name)
|
an := strings.ToLower(args[i].Name)
|
||||||
if _, ok := ad[an]; ok {
|
if _, ok := sel.Args[an]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,7 +365,7 @@ func (com *Compiler) compileArgs(sel *Select, args []*Arg) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ad[an] = struct{}{}
|
sel.Args[an] = args[i].Val
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -380,7 +378,7 @@ type expT struct {
|
||||||
|
|
||||||
func (com *Compiler) compileArgObj(arg *Arg) (*Exp, error) {
|
func (com *Compiler) compileArgObj(arg *Arg) (*Exp, error) {
|
||||||
if arg.Val.Type != nodeObj {
|
if arg.Val.Type != nodeObj {
|
||||||
return nil, fmt.Errorf("[Where] expecting an object")
|
return nil, fmt.Errorf("expecting an object")
|
||||||
}
|
}
|
||||||
|
|
||||||
return com.compileArgNode(arg.Val)
|
return com.compileArgNode(arg.Val)
|
||||||
|
@ -400,13 +398,14 @@ func (com *Compiler) compileArgNode(val *Node) (*Exp, error) {
|
||||||
intf := st.Pop()
|
intf := st.Pop()
|
||||||
eT, ok := intf.(*expT)
|
eT, ok := intf.(*expT)
|
||||||
if !ok || eT == nil {
|
if !ok || eT == nil {
|
||||||
return nil, fmt.Errorf("[Where] unexpected value poped out %v", intf)
|
return nil, fmt.Errorf("unexpected value poped out %v", intf)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(eT.node.Name) != 0 &&
|
if len(eT.node.Name) != 0 {
|
||||||
com.bl != nil && com.bl.MatchString(eT.node.Name) {
|
if _, ok := com.bl[strings.ToLower(eT.node.Name)]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ex, err := newExp(st, eT)
|
ex, err := newExp(st, eT)
|
||||||
|
|
||||||
|
@ -457,8 +456,6 @@ func (com *Compiler) compileArgSearch(sel *Select, arg *Arg) error {
|
||||||
Val: arg.Val.Val,
|
Val: arg.Val.Val,
|
||||||
}
|
}
|
||||||
|
|
||||||
sel.Args = append(sel.Args, arg)
|
|
||||||
|
|
||||||
if sel.Where != nil {
|
if sel.Where != nil {
|
||||||
sel.Where = &Exp{Op: OpAnd, Children: []*Exp{ex, sel.Where}}
|
sel.Where = &Exp{Op: OpAnd, Children: []*Exp{ex, sel.Where}}
|
||||||
} else {
|
} else {
|
||||||
|
@ -507,7 +504,7 @@ func (com *Compiler) compileArgOrderBy(sel *Select, arg *Arg) error {
|
||||||
return fmt.Errorf("OrderBy: unexpected value poped out %v", intf)
|
return fmt.Errorf("OrderBy: unexpected value poped out %v", intf)
|
||||||
}
|
}
|
||||||
|
|
||||||
if com.bl != nil && com.bl.MatchString(node.Name) {
|
if _, ok := com.bl[strings.ToLower(node.Name)]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -547,7 +544,7 @@ func (com *Compiler) compileArgOrderBy(sel *Select, arg *Arg) error {
|
||||||
func (com *Compiler) compileArgDistinctOn(sel *Select, arg *Arg) error {
|
func (com *Compiler) compileArgDistinctOn(sel *Select, arg *Arg) error {
|
||||||
node := arg.Val
|
node := arg.Val
|
||||||
|
|
||||||
if com.bl != nil && com.bl.MatchString(node.Name) {
|
if _, ok := com.bl[strings.ToLower(node.Name)]; ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
package qcode
|
package qcode
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewBlacklist(list []string) *regexp.Regexp {
|
func NewBlacklist(list []string) Blacklist {
|
||||||
var bl *regexp.Regexp
|
bl := make(map[string]struct{}, len(list))
|
||||||
|
|
||||||
if len(list) != 0 {
|
for i := range list {
|
||||||
re := fmt.Sprintf("(?i)%s", strings.Join(list, "|"))
|
bl[strings.ToLower(list[i])] = struct{}{}
|
||||||
bl = regexp.MustCompile(re)
|
|
||||||
}
|
}
|
||||||
return bl
|
return bl
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue