// Aggregating-projection helpers: substituting grouping-key subexpressions// with references to their output columns, and compiling a top-level// aggregate call into an AggCol. Split from proj_bind.go, which holds the// projection binding and nested-aggregate extraction.
package planimport ()// groupCol is one grouping item of an aggregating projection: its output// column index, name, and source expression.type groupCol struct { idx int name string expr ast.Expr}// alphaLocal renames the binder local *v to a fresh name when it collides// with a grouping column's output name, rewriting the bodies in place --// substituting a column reference into the body can then never be captured// by the local. avoid carries sibling locals of the same binder so the// fresh name cannot collide with them either; nil bodies are skipped.func alphaLocal( *string, []groupCol, []string, ...*ast.Expr) { := falsefor , := range {if .name == * { = truebreak } }if ! {return } := map[string]bool{}for , := range { [] = true }for , := range {if * != nil {collectAllVars(*, ) } }for , := range { [.name] = truecollectAllVars(.expr, ) }varstringfor := 1; ; ++ { = fmt.Sprintf("%s__%d", *, )if ![] {break } }for , := range {if * != nil { * = renameFree(*, *, ) } } * = }// shadowFiltered returns groups minus the ones a binder's locals make// unusable in its sub-scope: a group whose key expression mentions a local// (the body reference means the local, not the key) or whose output name// IS a local (defense in depth -- alphaLocal renames such locals away// before this filter runs on the wired paths). The common case -- no// collision -- returns groups unchanged without allocating.func shadowFiltered( []groupCol, ...string) []groupCol { := func( groupCol) bool {for , := range {if == "" {continue }if .name == || MentionsVar(.expr, ) {returntrue } }returnfalse }for , := range {if () { := make([]groupCol, 0, len()-1) = append(, [:]...)for , := range [+1:] {if !() { = append(, ) } }return } }return}// substGroupKeys re-points wrapper subexpressions at the grouping columns:// a subexpression structurally equal to a grouping item's expression// becomes a reference to its output column. Aggregates were already pulled// out by extractNestedAggs, so no aggregate call remains in the wrapper.// Comprehension/quantifier/reduce bodies bind their own variables but// still evaluate post-projection, so the walk descends into them: a local// that collides with a group's output name is first alpha-renamed to a// fresh name (substituting the column reference could otherwise be// captured), and a group is dropped for a sub-scope when the binder// shadows a variable its key expression mentions -- that body reference// means the local, not the key. Correlated subquery interiors// (EXISTS/COUNT/pattern comprehensions) remain untouched: their filters// evaluate against the subquery's own pattern scope, and a composite key// appearing only there stays a bind error.func substGroupKeys( ast.Expr, []groupCol) ast.Expr {for , := range {ifexprEqual(.expr, ) {return &ast.Var{Name: .name} } }switch n := .(type) {// `var.key` where `var` is a grouping key projected under a different // name: re-point the property access at the output column // (`RETURN n AS m, n.x + count(*)`).case *ast.Prop:for , := range {if , := .expr.(*ast.Var); && .Name == .Var && .name != .Var {return &ast.PropOf{Base: &ast.Var{Name: .name}, Key: .Key} } }// `var:Label` where `var` is a grouping key projected under a different // name: the label test references its variable by name, so it needs the // same re-pointing as a property access (`RETURN c AS comp, count(*) + // (CASE WHEN c:Company THEN 1 ELSE 0 END) AS x`).case *ast.HasLabelExpr:for , := range {if , := .expr.(*ast.Var); && .Name == .Var && .name != .Var {return &ast.HasLabelExpr{Var: .name, Expr: .Expr} } }case *ast.Unary: .Expr = (.Expr, )case *ast.IsNull: .Expr = (.Expr, )case *ast.IsTruth: .Expr = (.Expr, )case *ast.IsTyped: .Expr = (.Expr, )case *ast.PropOf: .Base = (.Base, )case *ast.Binary: .LHS = (.LHS, ) .RHS = (.RHS, )case *ast.In: .Expr = (.Expr, ) .List = (.List, )case *ast.Index: .Base = (.Base, ) .Idx = (.Idx, )case *ast.Func:for := range .Args { .Args[] = (.Args[], ) }case *ast.ListExpr:for := range .Elems { .Elems[] = (.Elems[], ) }case *ast.Case:if .Operand != nil { .Operand = (.Operand, ) }for := range .Whens { .Whens[].Cond = (.Whens[].Cond, ) .Whens[].Result = (.Whens[].Result, ) }if .Else != nil { .Else = (.Else, ) }case *ast.Slice: .Base = (.Base, )if .From != nil { .From = (.From, ) }if .To != nil { .To = (.To, ) }// Sources evaluate in the outer scope; the bodies descend after any // local colliding with an output name is alpha-renamed away, with the // remaining shadowed keys filtered out of the group set (see above).case *ast.ListPred: .List = (.List, )alphaLocal(&.Var, , nil, &.Pred) .Pred = (.Pred, shadowFiltered(, .Var))case *ast.ListComp: .List = (.List, )alphaLocal(&.Var, , nil, &.Filter, &.Map) := shadowFiltered(, .Var)if .Filter != nil { .Filter = (.Filter, ) }if .Map != nil { .Map = (.Map, ) }case *ast.Reduce: .Init = (.Init, ) .List = (.List, )alphaLocal(&.Acc, , []string{.Var}, &.Body)alphaLocal(&.Var, , []string{.Acc}, &.Body) .Body = (.Body, shadowFiltered(, .Acc, .Var))case *ast.MapLit:for := range .Fields { .Fields[].Val = (.Fields[].Val, ) }case *ast.MapProj:// The projected variable is held by name, so it re-points like a // property access or label test when its key is renamed.for , := range {if , := .expr.(*ast.Var); && .Name == .Var && .name != .Var { .Var = .namebreak } }for := range .Entries {if .Entries[].Kind == ast.MapProjField { .Entries[].Expr = (.Entries[].Expr, ) } } }return}// extractAgg compiles a top-level aggregate projection expression.func extractAgg( ast.Expr, int) (AggCol, error) { , := .(*ast.Func)if ! {returnAggCol{}, planErrf("an aggregate must be a top-level projection expression") }varAggKindswitchlower(.Name) {case"count": = AggCountcase"sum": = AggSumcase"avg": = AggAvgcase"min": = AggMincase"max": = AggMaxcase"collect", "collect_list": = AggCollectcase"stddev_samp": = AggStddevSampcase"stddev_pop": = AggStddevPopcase"percentile_cont", "percentilecont": = AggPercentileContcase"percentile_disc", "percentiledisc": = AggPercentileDiscdefault:returnAggCol{}, planErrf("aggregate function `%s` is not supported (Tier 1)", .Name) }if == AggPercentileCont || == AggPercentileDisc {if .Star || len(.Args) != 2 {returnAggCol{}, planErrf("`%s(...)` takes exactly two arguments (a value and a percentile)", .Name) }// The percentile is per-query, not per-row: a constant literal // (number or parameter) keeps the accumulator single-valued.if , := .Args[1].(*ast.Lit); ! {returnAggCol{}, planErrf("`%s(...)` requires a constant percentile", .Name) }returnAggCol{Kind: , Arg: .Args[0], Arg2: .Args[1], Distinct: .Distinct, OutIdx: }, nil }varast.Exprswitch {case .Star:if != AggCount {returnAggCol{}, planErrf("`%s(*)` is not valid", .Name) }caselen(.Args) == 1: = .Args[0]default:returnAggCol{}, planErrf("`%s(...)` takes exactly one argument (Tier 1)", .Name) }returnAggCol{Kind: , Arg: , Distinct: .Distinct, OutIdx: }, nil}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.