From f4f6420a302a966474d209f42a130626cb6c2370 Mon Sep 17 00:00:00 2001 From: Vikram Rangnekar Date: Mon, 8 Jun 2020 19:28:22 -0400 Subject: [PATCH] fix: race in node pool object cleanup --- core/bench.11 | 41 -------- core/internal/allow/allow.go | 20 ++-- core/internal/psql/query.go | 4 +- core/internal/qcode/bench.11 | 13 +++ core/internal/qcode/parse.go | 59 ++--------- core/internal/qcode/qcode.go | 198 ++++++++++++++++++++++------------- 6 files changed, 155 insertions(+), 180 deletions(-) delete mode 100644 core/bench.11 create mode 100644 core/internal/qcode/bench.11 diff --git a/core/bench.11 b/core/bench.11 deleted file mode 100644 index 95233cd..0000000 --- a/core/bench.11 +++ /dev/null @@ -1,41 +0,0 @@ -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] diff --git a/core/internal/allow/allow.go b/core/internal/allow/allow.go index 0e0c821..e5a8724 100644 --- a/core/internal/allow/allow.go +++ b/core/internal/allow/allow.go @@ -299,9 +299,13 @@ func (al *List) save(item Item) error { for _, v := range list { if v.Comment != "" { - f.WriteString(fmt.Sprintf("/* %s */\n\n", v.Comment)) + _, err = f.WriteString(fmt.Sprintf("/* %s */\n\n", v.Comment)) } else { - f.WriteString(fmt.Sprintf("/* %s */\n\n", v.Name)) + _, err = f.WriteString(fmt.Sprintf("/* %s */\n\n", v.Name)) + } + + if err != nil { + return err } if v.Vars != "" { @@ -320,18 +324,6 @@ func (al *List) save(item Item) error { return nil } -func matchPrefix(b []byte, i int, s string) bool { - if (len(b) - i) < len(s) { - return false - } - for n := 0; n < len(s); n++ { - if b[(i+n)] != s[n] { - return false - } - } - return true -} - func QueryName(b string) string { state, s := 0, 0 diff --git a/core/internal/psql/query.go b/core/internal/psql/query.go index 77ee4eb..1d0c255 100644 --- a/core/internal/psql/query.go +++ b/core/internal/psql/query.go @@ -220,10 +220,8 @@ func (co *Compiler) compileQueryWithMetadata( } if len(sel.Args) != 0 { - i := 0 for _, v := range sel.Args { - qcode.FreeNode(v, 500) - i++ + qcode.FreeNode(v) } } } diff --git a/core/internal/qcode/bench.11 b/core/internal/qcode/bench.11 new file mode 100644 index 0000000..e1f71fd --- /dev/null +++ b/core/internal/qcode/bench.11 @@ -0,0 +1,13 @@ +goos: darwin +goarch: amd64 +pkg: github.com/dosco/super-graph/core/internal/qcode +BenchmarkQCompile-16 118282 9686 ns/op 4031 B/op 30 allocs/op +BenchmarkQCompileP-16 427531 2710 ns/op 4077 B/op 30 allocs/op +BenchmarkQCompileFragment-16 140588 8328 ns/op 8903 B/op 13 allocs/op +BenchmarkParse-16 131396 9212 ns/op 4175 B/op 18 allocs/op +BenchmarkParseP-16 503778 2310 ns/op 4176 B/op 18 allocs/op +BenchmarkParseFragment-16 143725 8158 ns/op 10193 B/op 9 allocs/op +BenchmarkSchemaParse-16 240609 5060 ns/op 3968 B/op 57 allocs/op +BenchmarkSchemaParseP-16 785116 1534 ns/op 3968 B/op 57 allocs/op +PASS +ok github.com/dosco/super-graph/core/internal/qcode 11.092s diff --git a/core/internal/qcode/parse.go b/core/internal/qcode/parse.go index ce09932..6fe0eda 100644 --- a/core/internal/qcode/parse.go +++ b/core/internal/qcode/parse.go @@ -1,7 +1,6 @@ package qcode import ( - "encoding/binary" "errors" "fmt" "hash/maphash" @@ -80,6 +79,7 @@ type Field struct { type Arg struct { Name string Val *Node + df bool } type Node struct { @@ -182,8 +182,8 @@ func (p *Parser) parseFragment(op *Operation) error { frag := fragPool.Get().(*Fragment) frag.Reset() - frag.Fields = frag.fieldsA[:0] - frag.Args = frag.argsA[:0] + frag.Fields = frag.SelectionSet.fieldsA[:0] + frag.Args = frag.SelectionSet.argsA[:0] if p.peek(itemName) { frag.Name = p.val(p.next()) @@ -273,7 +273,7 @@ func (p *Parser) parseOpTypeAndArgs(op *Operation) error { op.Type = opSub } - op.Args = op.argsA[:0] + op.Args = op.SelectionSet.argsA[:0] var err error @@ -370,11 +370,11 @@ func (p *Parser) parseFields(fields []Field) ([]Field, error) { if isFrag { name := p.val(p.next()) - p.h.WriteString(name) - k := p.h.Sum64() + _, _ = p.h.WriteString(name) + id := p.h.Sum64() p.h.Reset() - fr, ok := p.frags[k] + fr, ok := p.frags[id] if !ok { return nil, fmt.Errorf("no fragment named '%s' defined", name) } @@ -403,6 +403,9 @@ func (p *Parser) parseFields(fields []Field) ([]Field, error) { f.Children = make([]int32, len(f.Children)) copy(f.Children, fr.Fields[i].Children) + f.Args = make([]Arg, len(f.Args)) + copy(f.Args, fr.Fields[i].Args) + // Update all the children which is needed. for j := range f.Children { f.Children[j] += n @@ -676,11 +679,6 @@ func (p *Parser) ignore() { p.pos = n } -func (p *Parser) peekCurrent() string { - item := p.items[p.pos] - return b2s(p.input[item.pos:item.end]) -} - func (p *Parser) peekNext() string { item := p.items[p.pos+1] return b2s(p.input[item.pos:item.end]) @@ -690,16 +688,6 @@ func (p *Parser) reset(to int) { p.pos = to } -func (p *Parser) fHash(name string, parentID int32) uint64 { - var b []byte - binary.LittleEndian.PutUint32(b, uint32(parentID)) - p.h.WriteString(name) - p.h.Write(b) - v := p.h.Sum64() - p.h.Reset() - return v -} - func b2s(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } @@ -736,31 +724,6 @@ func (t parserType) String() string { return v } -// type Frees struct { -// n *Node -// loc int -// } - -// var freeList []Frees - -// func FreeNode(n *Node, loc int) { -// j := -1 - -// for i := range freeList { -// if n == freeList[i].n { -// j = i -// break -// } -// } - -// if j == -1 { -// nodePool.Put(n) -// freeList = append(freeList, Frees{n, loc}) -// } else { -// fmt.Printf("(%d) RE_FREE %d %p %s %s\n", loc, freeList[j].loc, freeList[j].n, n.Name, n.Type) -// } -// } - -func FreeNode(n *Node, loc int) { +func FreeNode(n *Node) { nodePool.Put(n) } diff --git a/core/internal/qcode/qcode.go b/core/internal/qcode/qcode.go index c611eb0..bcfe9ae 100644 --- a/core/internal/qcode/qcode.go +++ b/core/internal/qcode/qcode.go @@ -277,6 +277,7 @@ func (com *Compiler) Compile(query []byte, role string) (*QCode, error) { return nil, err } + freeNodes(op) opPool.Put(op) return &qc, nil @@ -492,50 +493,42 @@ func (com *Compiler) AddFilters(qc *QCode, sel *Select, role string) { func (com *Compiler) compileArgs(qc *QCode, sel *Select, args []Arg, role string) error { var err error - // don't free this arg either previously done or will be free'd - // in the future like in psql - var df bool - for i := range args { arg := &args[i] switch arg.Name { case "id": - err, df = com.compileArgID(sel, arg) + err = com.compileArgID(sel, arg) case "search": - err, df = com.compileArgSearch(sel, arg) + err = com.compileArgSearch(sel, arg) case "where": - err, df = com.compileArgWhere(sel, arg, role) + err = com.compileArgWhere(sel, arg, role) case "orderby", "order_by", "order": - err, df = com.compileArgOrderBy(sel, arg) + err = com.compileArgOrderBy(sel, arg) case "distinct_on", "distinct": - err, df = com.compileArgDistinctOn(sel, arg) + err = com.compileArgDistinctOn(sel, arg) case "limit": - err, df = com.compileArgLimit(sel, arg) + err = com.compileArgLimit(sel, arg) case "offset": - err, df = com.compileArgOffset(sel, arg) + err = com.compileArgOffset(sel, arg) case "first": - err, df = com.compileArgFirstLast(sel, arg, PtForward) + err = com.compileArgFirstLast(sel, arg, PtForward) case "last": - err, df = com.compileArgFirstLast(sel, arg, PtBackward) + err = com.compileArgFirstLast(sel, arg, PtBackward) case "after": - err, df = com.compileArgAfterBefore(sel, arg, PtForward) + err = com.compileArgAfterBefore(sel, arg, PtForward) case "before": - err, df = com.compileArgAfterBefore(sel, arg, PtBackward) - } - - if !df { - FreeNode(arg.Val, 5) + err = com.compileArgAfterBefore(sel, arg, PtBackward) } if err != nil { @@ -646,39 +639,20 @@ func (com *Compiler) compileArgNode(st *util.Stack, node *Node, usePool bool) (* } } - if usePool { - st.Push(node) - - for { - if st.Len() == 0 { - break - } - intf := st.Pop() - node, ok := intf.(*Node) - if !ok || node == nil { - continue - } - for i := range node.Children { - st.Push(node.Children[i]) - } - FreeNode(node, 1) - } - } - return root, needsUser, nil } -func (com *Compiler) compileArgID(sel *Select, arg *Arg) (error, bool) { +func (com *Compiler) compileArgID(sel *Select, arg *Arg) error { if sel.ID != 0 { - return nil, false + return nil } if sel.Where != nil && sel.Where.Op == OpEqID { - return nil, false + return nil } if arg.Val.Type != NodeVar { - return argErr("id", "variable"), false + return argErr("id", "variable") } ex := expPool.Get().(*Exp) @@ -689,12 +663,12 @@ func (com *Compiler) compileArgID(sel *Select, arg *Arg) (error, bool) { ex.Val = arg.Val.Val sel.Where = ex - return nil, false + return nil } -func (com *Compiler) compileArgSearch(sel *Select, arg *Arg) (error, bool) { +func (com *Compiler) compileArgSearch(sel *Select, arg *Arg) error { if arg.Val.Type != NodeVar { - return argErr("search", "variable"), false + return argErr("search", "variable") } ex := expPool.Get().(*Exp) @@ -709,18 +683,19 @@ func (com *Compiler) compileArgSearch(sel *Select, arg *Arg) (error, bool) { } sel.Args[arg.Name] = arg.Val + arg.df = true AddFilter(sel, ex) - return nil, true + return nil } -func (com *Compiler) compileArgWhere(sel *Select, arg *Arg, role string) (error, bool) { +func (com *Compiler) compileArgWhere(sel *Select, arg *Arg, role string) error { st := util.NewStack() var err error ex, nu, err := com.compileArgObj(st, arg) if err != nil { - return err, false + return err } if nu && role == "anon" { @@ -728,12 +703,12 @@ func (com *Compiler) compileArgWhere(sel *Select, arg *Arg, role string) (error, } AddFilter(sel, ex) - return nil, true + return nil } -func (com *Compiler) compileArgOrderBy(sel *Select, arg *Arg) (error, bool) { +func (com *Compiler) compileArgOrderBy(sel *Select, arg *Arg) error { if arg.Val.Type != NodeObj { - return fmt.Errorf("expecting an object"), false + return fmt.Errorf("expecting an object") } st := util.NewStack() @@ -751,16 +726,15 @@ func (com *Compiler) compileArgOrderBy(sel *Select, arg *Arg) (error, bool) { node, ok := intf.(*Node) if !ok || node == nil { - return fmt.Errorf("17: unexpected value %v (%t)", intf, intf), false + return fmt.Errorf("17: unexpected value %v (%t)", intf, intf) } if _, ok := com.bl[node.Name]; ok { - FreeNode(node, 2) continue } if node.Type != NodeStr && node.Type != NodeVar { - return fmt.Errorf("expecting a string or variable"), false + return fmt.Errorf("expecting a string or variable") } ob := &OrderBy{} @@ -779,25 +753,24 @@ func (com *Compiler) compileArgOrderBy(sel *Select, arg *Arg) (error, bool) { case "desc_nulls_last": ob.Order = OrderDescNullsLast default: - return fmt.Errorf("valid values include asc, desc, asc_nulls_first and desc_nulls_first"), false + return fmt.Errorf("valid values include asc, desc, asc_nulls_first and desc_nulls_first") } setOrderByColName(ob, node) sel.OrderBy = append(sel.OrderBy, ob) - FreeNode(node, 3) } - return nil, false + return nil } -func (com *Compiler) compileArgDistinctOn(sel *Select, arg *Arg) (error, bool) { +func (com *Compiler) compileArgDistinctOn(sel *Select, arg *Arg) error { node := arg.Val if _, ok := com.bl[node.Name]; ok { - return nil, false + return nil } if node.Type != NodeList && node.Type != NodeStr { - return fmt.Errorf("expecting a list of strings or just a string"), false + return fmt.Errorf("expecting a list of strings or just a string") } if node.Type == NodeStr { @@ -806,58 +779,57 @@ func (com *Compiler) compileArgDistinctOn(sel *Select, arg *Arg) (error, bool) { for i := range node.Children { sel.DistinctOn = append(sel.DistinctOn, node.Children[i].Val) - FreeNode(node.Children[i], 5) } - return nil, false + return nil } -func (com *Compiler) compileArgLimit(sel *Select, arg *Arg) (error, bool) { +func (com *Compiler) compileArgLimit(sel *Select, arg *Arg) error { node := arg.Val if node.Type != NodeInt { - return argErr("limit", "number"), false + return argErr("limit", "number") } sel.Paging.Limit = node.Val - return nil, false + return nil } -func (com *Compiler) compileArgOffset(sel *Select, arg *Arg) (error, bool) { +func (com *Compiler) compileArgOffset(sel *Select, arg *Arg) error { node := arg.Val if node.Type != NodeVar { - return argErr("offset", "variable"), false + return argErr("offset", "variable") } sel.Paging.Offset = node.Val - return nil, false + return nil } -func (com *Compiler) compileArgFirstLast(sel *Select, arg *Arg, pt PagingType) (error, bool) { +func (com *Compiler) compileArgFirstLast(sel *Select, arg *Arg, pt PagingType) error { node := arg.Val if node.Type != NodeInt { - return argErr(arg.Name, "number"), false + return argErr(arg.Name, "number") } sel.Paging.Type = pt sel.Paging.Limit = node.Val - return nil, false + return nil } -func (com *Compiler) compileArgAfterBefore(sel *Select, arg *Arg, pt PagingType) (error, bool) { +func (com *Compiler) compileArgAfterBefore(sel *Select, arg *Arg, pt PagingType) error { node := arg.Val if node.Type != NodeVar || node.Val != "cursor" { - return fmt.Errorf("value for argument '%s' must be a variable named $cursor", arg.Name), false + return fmt.Errorf("value for argument '%s' must be a variable named $cursor", arg.Name) } sel.Paging.Type = pt sel.Paging.Cursor = true - return nil, false + return nil } // var zeroTrv = &trval{} @@ -1237,3 +1209,81 @@ func FreeExp(ex *Exp) { func argErr(name, ty string) error { return fmt.Errorf("value for argument '%s' must be a %s", name, ty) } + +func freeNodes(op *Operation) { + var st *util.Stack + fm := make(map[*Node]struct{}) + + for i := range op.Args { + arg := op.Args[i] + if arg.df { + continue + } + + for i := range arg.Val.Children { + if st == nil { + st = util.NewStack() + } + c := arg.Val.Children[i] + if _, ok := fm[c]; !ok { + st.Push(c) + } + } + + if _, ok := fm[arg.Val]; !ok { + nodePool.Put(arg.Val) + fm[arg.Val] = struct{}{} + } + + } + + for i := range op.Fields { + f := op.Fields[i] + + for j := range f.Args { + arg := f.Args[j] + if arg.df { + continue + } + + for k := range arg.Val.Children { + if st == nil { + st = util.NewStack() + } + c := arg.Val.Children[k] + if _, ok := fm[c]; !ok { + st.Push(c) + } + } + + if _, ok := fm[arg.Val]; !ok { + nodePool.Put(arg.Val) + fm[arg.Val] = struct{}{} + } + } + } + + if st == nil { + return + } + + for { + if st.Len() == 0 { + break + } + intf := st.Pop() + node, ok := intf.(*Node) + if !ok || node == nil { + continue + } + + for i := range node.Children { + st.Push(node.Children[i]) + } + + if _, ok := fm[node]; !ok { + nodePool.Put(node) + fm[node] = struct{}{} + } + } +}