package plan
import (
"github.com/freeeve/gochickpeas/gql/internal/ast"
"github.com/freeeve/gochickpeas/gql/internal/semantics"
)
func pureProjectionSubst(proj *ast .Projection ) (map [string ]ast .Expr , bool ) {
if proj .Distinct || len (proj .OrderBy ) > 0 || proj .Skip != nil || proj .Limit != nil {
return nil , false
}
subst := map [string ]ast .Expr {}
names := map [string ]bool {}
for _ , item := range proj .Items {
if semantics .ExprHasAgg (item .Expr ) {
return nil , false
}
var name string
v , isVar := item .Expr .(*ast .Var )
switch {
case item .Alias != "" :
name = item .Alias
case isVar :
name = v .Name
default :
return nil , false
}
if names [name ] {
return nil , false
}
names [name ] = true
identity := isVar && v .Name == name
if !identity {
subst [name ] = item .Expr
}
}
return subst , true
}
func projectionIsAggregated(proj *ast .Projection ) bool {
if proj .Star {
return false
}
for _ , it := range proj .Items {
if semantics .ExprHasAgg (it .Expr ) {
return true
}
}
return false
}
func substExpr(e ast .Expr , subst map [string ]ast .Expr ) (ast .Expr , bool ) {
switch n := e .(type ) {
case *ast .Lit :
return e , true
case *ast .Var :
if rep , ok := subst [n .Name ]; ok {
return rep , true
}
return e , true
case *ast .Prop :
rep , ok := subst [n .Var ]
if !ok {
return e , true
}
if v , isVar := rep .(*ast .Var ); isVar {
return &ast .Prop {Var : v .Name , Key : n .Key }, true
}
return nil , false
case *ast .HasLabelExpr :
rep , ok := subst [n .Var ]
if !ok {
return e , true
}
if v , isVar := rep .(*ast .Var ); isVar {
return &ast .HasLabelExpr {Var : v .Name , Expr : n .Expr }, true
}
return nil , false
case *ast .Unary :
x , ok := substExpr (n .Expr , subst )
if !ok {
return nil , false
}
return &ast .Unary {Op : n .Op , Expr : x }, true
case *ast .IsNull :
x , ok := substExpr (n .Expr , subst )
if !ok {
return nil , false
}
return &ast .IsNull {Expr : x , Negated : n .Negated }, true
case *ast .IsTruth :
x , ok := substExpr (n .Expr , subst )
if !ok {
return nil , false
}
return &ast .IsTruth {Expr : x , Want : n .Want , Negated : n .Negated }, true
case *ast .IsTyped :
x , ok := substExpr (n .Expr , subst )
if !ok {
return nil , false
}
return &ast .IsTyped {Expr : x , Kind : n .Kind , Negated : n .Negated }, true
case *ast .Binary :
l , ok := substExpr (n .LHS , subst )
if !ok {
return nil , false
}
r , ok := substExpr (n .RHS , subst )
if !ok {
return nil , false
}
return &ast .Binary {Op : n .Op , LHS : l , RHS : r }, true
case *ast .In :
x , ok := substExpr (n .Expr , subst )
if !ok {
return nil , false
}
l , ok := substExpr (n .List , subst )
if !ok {
return nil , false
}
return &ast .In {Expr : x , List : l }, true
case *ast .Func :
if n .Star {
return e , true
}
mapped := make ([]ast .Expr , len (n .Args ))
for i , a := range n .Args {
m , ok := substExpr (a , subst )
if !ok {
return nil , false
}
mapped [i ] = m
}
return &ast .Func {Name : n .Name , Distinct : n .Distinct , Args : mapped }, true
case *ast .ListExpr :
mapped := make ([]ast .Expr , len (n .Elems ))
for i , a := range n .Elems {
m , ok := substExpr (a , subst )
if !ok {
return nil , false
}
mapped [i ] = m
}
return &ast .ListExpr {Elems : mapped }, true
case *ast .Case :
out := &ast .Case {}
if n .Operand != nil {
o , ok := substExpr (n .Operand , subst )
if !ok {
return nil , false
}
out .Operand = o
}
out .Whens = make ([]ast .CaseWhen , len (n .Whens ))
for i , w := range n .Whens {
c , ok := substExpr (w .Cond , subst )
if !ok {
return nil , false
}
r , ok := substExpr (w .Result , subst )
if !ok {
return nil , false
}
out .Whens [i ] = ast .CaseWhen {Cond : c , Result : r }
}
if n .Else != nil {
x , ok := substExpr (n .Else , subst )
if !ok {
return nil , false
}
out .Else = x
}
return out , true
}
return nil , false
}
func inlineProjection(proj *ast .Projection , where ast .Expr , subst map [string ]ast .Expr ) (*ast .With , bool ) {
out := ast .Projection {
Star : proj .Star ,
Distinct : proj .Distinct ,
Items : make ([]ast .ReturnItem , len (proj .Items )),
OrderBy : make ([]ast .SortItem , len (proj .OrderBy )),
Skip : proj .Skip ,
Limit : proj .Limit ,
}
for i , item := range proj .Items {
alias := item .Alias
if alias == "" {
alias = semantics .DerivedName (item .Expr )
}
x , ok := substExpr (item .Expr , subst )
if !ok {
return nil , false
}
out .Items [i ] = ast .ReturnItem {Expr : x , Alias : alias }
}
for i , s := range proj .OrderBy {
x , ok := substExpr (s .Expr , subst )
if !ok {
return nil , false
}
out .OrderBy [i ] = ast .SortItem {Expr : x , Desc : s .Desc }
}
var outWhere ast .Expr
if where != nil {
x , ok := substExpr (where , subst )
if !ok {
return nil , false
}
outWhere = x
}
return &ast .With {Proj : out , Where : outWhere }, true
}
func fuseTrailingProjectionIntoRet(clauses []ast .Clause , ret ast .Projection ) ([]ast .Clause , ast .Projection ) {
for {
n := len (clauses )
if n == 0 || !projectionIsAggregated (&ret ) {
return clauses , ret
}
w , ok := clauses [n -1 ].(*ast .With )
if !ok || w .Where != nil {
return clauses , ret
}
subst , pure := pureProjectionSubst (&w .Proj )
if !pure {
return clauses , ret
}
fused , okf := inlineProjection (&ret , nil , subst )
if !okf {
return clauses , ret
}
clauses , ret = clauses [:n -1 ], fused .Proj
}
}
func fuseProjectionBeforeAggregate(clauses []ast .Clause ) []ast .Clause {
for {
fused := fuseProjectionBeforeAggregateOnce (clauses )
if len (fused ) == len (clauses ) {
return fused
}
clauses = fused
}
}
func fuseProjectionBeforeAggregateOnce(clauses []ast .Clause ) []ast .Clause {
out := make ([]ast .Clause , 0 , len (clauses ))
for i := 0 ; i < len (clauses ); i ++ {
w , ok := clauses [i ].(*ast .With )
if ok && w .Where == nil {
if subst , pure := pureProjectionSubst (&w .Proj ); pure && i +1 < len (clauses ) {
if next , isWith := clauses [i +1 ].(*ast .With ); isWith && projectionIsAggregated (&next .Proj ) {
if fused , okf := inlineProjection (&next .Proj , next .Where , subst ); okf {
out = append (out , fused )
i ++
continue
}
}
}
}
out = append (out , clauses [i ])
}
return out
}
The pages are generated with Golds v0.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 .