featurebase/sql3/planner/planwalker.go
Travis Turner aa17b8d725
Enable linter: stylecheck (#2317)
* Enable linter: stylecheck

This enabled the stylecheck linter, but excludes some staticchecks for
now. The following are ignored because they will take a bit of time to
address, but the intention is to address them and remove them from the
exclusion list.

ST1000: at least one file in a package should have a package comment
ST1003: golang naming standards
ST1008: error should be returned as the last argument
ST1016: methods on the same type should have the same receiver name
ST1020: comment on exported function

* Address ST1015

For some reason this failed in CI but not locally. I can't figure out
why that check isn't happening locally. This just moves the switch
statements around so that the `default` is the first (or last) item.

* Adjust error string in test to match case-adjusted error

* Remove TestCloseTimeout
2023-03-14 08:45:18 -05:00

366 lines
11 KiB
Go

package planner
import (
"github.com/featurebasedb/featurebase/v3/sql3/planner/types"
)
// PlanVisitor visits nodes in the plan.
type PlanVisitor interface {
// VisitOperator method is invoked for each node during PlanWalk. If the
// resulting PlanVisitor is not nil, PlanWalk visits each of the children of
// the node with that visitor, followed by a call of VisitOperator(nil) to
// the returned visitor.
VisitOperator(op types.PlanOperator) PlanVisitor
}
// PlanWalk traverses the plan depth-first. It starts by calling
// v.VisitOperator; node must not be nil. If the result returned by
// v.VisitOperator is not nil, PlanWalk is invoked recursively with the returned
// result for each of the children of the plan operator, followed by a call of
// v.VisitOperator(nil) to the returned result. If v.VisitOperator(op) returns
// non-nil, then all children are walked, even if one of them returns nil.
func PlanWalk(v PlanVisitor, op types.PlanOperator) {
if v = v.VisitOperator(op); v == nil {
return
}
for _, child := range op.Children() {
PlanWalk(v, child)
}
v.VisitOperator(nil)
}
type planInspectionFunction func(types.PlanOperator) bool
func (f planInspectionFunction) VisitOperator(op types.PlanOperator) PlanVisitor {
if f(op) {
return f
}
return nil
}
// InspectPlan traverses the plan op graph depth-first order
// if f(op) returns true, InspectPlan invokes f recursively for each of the children of op,
// followed by a call of f(nil).
func InspectPlan(op types.PlanOperator, f planInspectionFunction) {
PlanWalk(f, op)
}
//-----------------------------------------------------------------------------
// ExprVisitor visits expressions in an expression tree.
type ExprVisitor interface {
// VisitExpr method is invoked for each expr encountered by ExprWalk.
// If the result is not nil, ExprWalk visits each of the children
// of the expr, followed by a call of VisitExpr(nil) to the returned result.
VisitExpr(expr types.PlanExpression) ExprVisitor
}
func ExprWalk(v ExprVisitor, expr types.PlanExpression) {
if v = v.VisitExpr(expr); v == nil {
return
}
for _, child := range expr.Children() {
ExprWalk(v, child)
}
v.VisitExpr(nil)
}
type exprInspector func(types.PlanExpression) bool
func (f exprInspector) VisitExpr(e types.PlanExpression) ExprVisitor {
if f(e) {
return f
}
return nil
}
// walkExpressions traverses the plan and calls ExprWalk on any expression it finds
func walkExpressions(v ExprVisitor, op types.PlanOperator) {
InspectPlan(op, func(operator types.PlanOperator) bool {
if n, ok := operator.(types.ContainsExpressions); ok {
for _, e := range n.Expressions() {
ExprWalk(v, e)
}
}
return true
})
}
// InspectOperatorExpressions traverses the plan and calls WalkExpressions on any
// expression it finds.
func InspectOperatorExpressions(op types.PlanOperator, f exprInspector) {
walkExpressions(f, op)
}
// InspectExpression traverses expressions in depth-first order
func InspectExpression(expr types.PlanExpression, f func(expr types.PlanExpression) bool) {
ExprWalk(exprInspector(f), expr)
}
//-----------------------------------------------------------------------------
// PlanOpExprVisitor visits expressions in an expression tree. Like ExprVisitor, but with the added context of the plan op in which
// an expression is embedded.
type PlanOpExprVisitor interface {
// VisitPlanOpExpr method is invoked for each expr encountered by Walk. If the result Visitor is not nil, Walk visits each of
// the children of the expr with that visitor, followed by a call of VisitPlanOpExpr(nil, nil) to the returned visitor.
VisitPlanOpExpr(op types.PlanOperator, expression types.PlanExpression) PlanOpExprVisitor
}
// ExprWithPlanOpWalk traverses the expression tree in depth-first order. It starts by calling v.VisitPlanOpExpr(op, expr); expr must
// not be nil. If the visitor returned by v.VisitPlanOpExpr(op, expr) is not nil, Walk is invoked recursively with the returned
// visitor for each children of the expr, followed by a call of v.VisitPlanOpExpr(nil, nil) to the returned visitor.
func ExprWithPlanOpWalk(v PlanOpExprVisitor, op types.PlanOperator, expr types.PlanExpression) {
if v = v.VisitPlanOpExpr(op, expr); v == nil {
return
}
for _, child := range expr.Children() {
ExprWithPlanOpWalk(v, op, child)
}
v.VisitPlanOpExpr(nil, nil)
}
type exprWithNodeInspector func(types.PlanOperator, types.PlanExpression) bool
func (f exprWithNodeInspector) VisitPlanOpExpr(op types.PlanOperator, expr types.PlanExpression) PlanOpExprVisitor {
if f(op, expr) {
return f
}
return nil
}
// WalkExpressionsWithPlanOp traverses the plan and calls ExprWithPlanOpWalk on any expression it finds.
func WalkExpressionsWithPlanOp(v PlanOpExprVisitor, op types.PlanOperator) {
InspectPlan(op, func(operator types.PlanOperator) bool {
if expressioner, ok := operator.(types.ContainsExpressions); ok {
for _, e := range expressioner.Expressions() {
ExprWithPlanOpWalk(v, operator, e)
}
}
return true
})
}
// InspectExpressionsWithPlanOp traverses the plan and calls f on any expression it finds.
func InspectExpressionsWithPlanOp(op types.PlanOperator, f exprWithNodeInspector) {
WalkExpressionsWithPlanOp(f, op)
}
// PlanOpTransformFunc is a function that given a plan op will return either a transformed plan op or the original plan op.
// If there was a transformation, the bool will be true, and an error if there was an error
type PlanOpTransformFunc func(op types.PlanOperator) (types.PlanOperator, bool, error)
// TransformPlanOp applies a depth first transformation function to the given plan op
// It returns a tuple that is the result of the transformation; the new PlanOperator, a bool that
// is true if the resultant PlanOperator has not been transformed or an error.
// If the TransformPlanOp has children it will iterate the children and call the transformation
// function on each of them in turn. If those operators are transformed, it will create a new operator
// with those children. The last step is to call the transformation on the passed PlanOperator
func TransformPlanOp(op types.PlanOperator, f PlanOpTransformFunc) (types.PlanOperator, bool, error) {
thisOperator := op
children := thisOperator.Children()
if len(children) == 0 {
return f(thisOperator)
}
var newChildren []types.PlanOperator
for i := range children {
child := children[i]
child, same, err := TransformPlanOp(child, f)
if err != nil {
return nil, true, err
}
if !same {
if newChildren == nil {
newChildren = make([]types.PlanOperator, len(children))
copy(newChildren, children)
}
newChildren[i] = child
}
}
var err error
sameChildren := true
if len(newChildren) > 0 {
sameChildren = false
thisOperator, err = thisOperator.WithChildren(newChildren...)
if err != nil {
return nil, true, err
}
}
resultOperator, sameOperator, err := f(thisOperator)
if err != nil {
return nil, true, err
}
return resultOperator, sameChildren && sameOperator, nil
}
// ParentContext is a struct that enables transformation functions to include a parent operator
type ParentContext struct {
Operator types.PlanOperator
Parent types.PlanOperator
ChildCount int
}
type ParentContextFunc func(c ParentContext) (types.PlanOperator, bool, error)
type ParentSelectorFunc func(c ParentContext) bool
// TransformPlanOpWithParent applies a transformation function to a plan operator in the context that plan operators parent
func TransformPlanOpWithParent(op types.PlanOperator, s ParentSelectorFunc, f ParentContextFunc) (types.PlanOperator, bool, error) {
return planOpWithParentHelper(ParentContext{op, nil, -1}, s, f)
}
func planOpWithParentHelper(c ParentContext, s ParentSelectorFunc, f ParentContextFunc) (types.PlanOperator, bool, error) {
operator := c.Operator
children := operator.Children()
if len(children) == 0 {
return f(c)
}
var (
newChildren []types.PlanOperator
err error
)
for i := range children {
child := children[i]
cc := ParentContext{child, operator, i}
if s == nil || s(cc) {
child, same, err := planOpWithParentHelper(cc, s, f)
if err != nil {
return nil, true, err
}
if !same {
if newChildren == nil {
newChildren = make([]types.PlanOperator, len(children))
copy(newChildren, children)
}
newChildren[i] = child
}
}
}
sameChildren := true
if len(newChildren) > 0 {
sameChildren = false
operator, err = operator.WithChildren(newChildren...)
if err != nil {
return nil, true, err
}
}
resultOperator, sameOperator, err := f(ParentContext{operator, c.Parent, c.ChildCount})
if err != nil {
return nil, true, err
}
return resultOperator, sameChildren && sameOperator, nil
}
// ExprWithPlanOpFunc is a function that given an expression and the node
// that contains it, will return that expression as is or transformed
// along with an error, if any.
type ExprWithPlanOpFunc func(op types.PlanOperator, expr types.PlanExpression) (types.PlanExpression, bool, error)
// ExprFunc is a function that given an expression will return that
// expression as is or transformed, or bool to indicate
// whether the expression was modified, and an error or nil.
type ExprFunc func(expr types.PlanExpression) (types.PlanExpression, bool, error)
// ExprSelectorFunc is a function that can be used as a filter selector during
// expression transformation - it is called before calling a transformation
// function on a child expression; if it returns false, the child is skipped
type ExprSelectorFunc func(parentExpr, childExpr types.PlanExpression) bool
// TransformSinglePlanOpExpressions applies a transformation function to all expressions on the given plan operator
func TransformSinglePlanOpExpressions(op types.PlanOperator, f ExprFunc, selector ExprSelectorFunc) (types.PlanOperator, bool, error) {
e, ok := op.(types.ContainsExpressions)
if !ok {
return op, true, nil
}
exprs := e.Expressions()
if len(exprs) == 0 {
return op, true, nil
}
var newExprs []types.PlanExpression
for i := range exprs {
expr := exprs[i]
expr, same, err := TransformExpr(expr, f, selector)
if err != nil {
return nil, true, err
}
if !same {
if newExprs == nil {
newExprs = make([]types.PlanExpression, len(exprs))
copy(newExprs, exprs)
}
newExprs[i] = expr
}
}
if len(newExprs) > 0 {
n, err := e.WithUpdatedExpressions(newExprs...)
if err != nil {
return nil, true, err
}
return n, false, nil
}
return op, true, nil
}
// TransformExpr applies a depth first transformation function to an expression
func TransformExpr(expr types.PlanExpression, transformFunction ExprFunc, selector ExprSelectorFunc) (types.PlanExpression, bool, error) {
thisExpr := expr
children := expr.Children()
if len(children) == 0 {
return transformFunction(thisExpr)
}
var (
newChildren []types.PlanExpression
err error
)
for i := 0; i < len(children); i++ {
child := children[i]
if selector(expr, child) {
c, same, err := TransformExpr(child, transformFunction, selector)
if err != nil {
return nil, true, err
}
if !same {
if newChildren == nil {
newChildren = make([]types.PlanExpression, len(children))
copy(newChildren, children)
}
newChildren[i] = c
}
}
}
sameChildren := true
if len(newChildren) > 0 {
sameChildren = false
thisExpr, err = thisExpr.WithChildren(newChildren...)
if err != nil {
return nil, true, err
}
}
resultExpr, sameExpr, err := transformFunction(thisExpr)
if err != nil {
return nil, true, err
}
return resultExpr, sameChildren && sameExpr, nil
}