func applyProcedures(ctx *sql.Context, a *Analyzer, n sql.Node, scope *Scope) (sql.Node, error) {
	if a.ProcedureCache.IsPopulating {
		return n, nil
	}
	if _, ok := n.(*plan.CreateProcedure); ok {
		return n, nil
	}
	return plan.TransformUp(n, func(n sql.Node) (sql.Node, error) {
		switch n := n.(type) {
		case *plan.Call:
			return applyProceduresCall(ctx, a, n, scope)
		case *plan.ShowProcedureStatus:
			return applyProceduresShowProcedure(ctx, a, n, scope)
		default:
			return n, nil
		}
	})
}
applyProcedures applies the relevant stored procedures to the node given (if necessary).
package analyzer
import (
	"fmt"
	"strings"
	"gopkg.in/src-d/go-errors.v1"
	"github.com/dolthub/go-mysql-server/sql"
	"github.com/dolthub/go-mysql-server/sql/expression"
	"github.com/dolthub/go-mysql-server/sql/parse"
	"github.com/dolthub/go-mysql-server/sql/plan"
)
func loadStoredProcedures(ctx *sql.Context, a *Analyzer, n sql.Node, scope *Scope) (sql.Node, error) {
	if a.ProcedureCache.IsPopulating {
		return n, nil
	}
	referencesProcedures := false
	plan.Inspect(n, func(n sql.Node) bool {
		if _, ok := n.(*plan.Call); ok {
			referencesProcedures = true
			return false
		} else if _, ok := n.(*plan.ShowProcedureStatus); ok {
			referencesProcedures = true
			return false
		}
		return true
	})
	if !referencesProcedures {
		return n, nil
	}
	a.ProcedureCache = NewProcedureCache()
	a.ProcedureCache.IsPopulating = true
	defer func() {
		a.ProcedureCache.IsPopulating = false
	}()
	for _, database := range a.Catalog.AllDatabases() {
		if pdb, ok := database.(sql.StoredProcedureDatabase); ok {
			procedures, err := pdb.GetStoredProcedures(ctx)
			if err != nil {
				return nil, err
			}
			for _, procedure := range procedures {
				parsedProcedure, err := parse.Parse(ctx, procedure.CreateStatement)
				if err != nil {
					return nil, err
				}
				cp, ok := parsedProcedure.(*plan.CreateProcedure)
				if !ok {
					return nil, sql.ErrProcedureCreateStatementInvalid.New(procedure.CreateStatement)
				}
				paramNames, err := validateStoredProcedure(ctx, cp.Procedure)
				if err != nil {
					return nil, err
				}
				analyzedNode, err := resolveDeclarations(ctx, a, cp.Procedure, scope)
				if err != nil {
					return nil, err
				}
				analyzedNode, err = resolveProcedureParams(ctx, paramNames, analyzedNode)
				if err != nil {
					return nil, err
				}
				analyzedNode, err = analyzeProcedureBodies(ctx, a, analyzedNode, false, scope)
				if err != nil {
					return nil, err
				}
				analyzedProc, ok := analyzedNode.(*plan.Procedure)
				if !ok {
					return nil, fmt.Errorf("analyzed node %T and expected *plan.Procedure", analyzedNode)
				}
				a.ProcedureCache.Register(database.Name(), analyzedProc)
			}
		}
	}
	return n, nil
}
func analyzeProcedureBodies(ctx *sql.Context, a *Analyzer, node sql.Node, skipCall bool, scope *Scope) (sql.Node, error) {
	children := node.Children()
	newChildren := make([]sql.Node, len(children))
	var err error
	for i, child := range children {
		var newChild sql.Node
		switch child := child.(type) {
		case *plan.Procedure, *plan.BeginEndBlock, *plan.Block, *plan.IfElseBlock, *plan.IfConditional:
			newChild, err = analyzeProcedureBodies(ctx, a, child, skipCall, scope)
		case *plan.Call:
			if skipCall {
				newChild = child
			} else {
				newChild, err = a.Analyze(ctx, child, scope)
			}
		default:
			newChild, err = a.Analyze(ctx, child, scope)
		}
		if err != nil {
			return nil, err
		}
		newChildren[i] = stripQueryProcess(newChild)
	}
	return node.WithChildren(newChildren...)
}
func validateCreateProcedure(ctx *sql.Context, a *Analyzer, node sql.Node, scope *Scope) (sql.Node, error) {
	cp, ok := node.(*plan.CreateProcedure)
	if !ok {
		return node, nil
	}
	paramNames, err := validateStoredProcedure(ctx, cp.Procedure)
	if err != nil {
		return nil, err
	}
	proc, err := resolveProcedureParams(ctx, paramNames, cp.Procedure)
	if err != nil {
		return nil, err
	}
	newProc, err := analyzeProcedureBodies(ctx, a, proc, true, nil)
	if err != nil {
		return nil, err
	}
	return cp.WithChildren(stripQueryProcess(newProc))
}
func validateStoredProcedure(ctx *sql.Context, proc *plan.Procedure) (map[string]struct{}, error) {
	paramNames := make(map[string]struct{})
	for _, param := range proc.Params {
		paramName := strings.ToLower(param.Name)
		if _, ok := paramNames[paramName]; ok {
			return nil, sql.ErrProcedureDuplicateParameterName.New(param.Name, proc.Name)
		}
		paramNames[paramName] = struct{}{}
	}
	var err error
	spUnsupportedErr := errors.NewKind("creating %s in stored procedures is currently unsupported " +
		"and will be added in a future release")
	plan.Inspect(proc, func(n sql.Node) bool {
		switch n.(type) {
		case *plan.CreateTable:
			err = spUnsupportedErr.New("tables")
		case *plan.CreateTrigger:
			err = spUnsupportedErr.New("triggers")
		case *plan.CreateProcedure:
			err = spUnsupportedErr.New("procedures")
		case *plan.CreateDB:
			err = spUnsupportedErr.New("databases")
		case *plan.CreateForeignKey:
			err = spUnsupportedErr.New("foreign keys")
		case *plan.CreateIndex:
			err = spUnsupportedErr.New("indexes")
		case *plan.CreateView:
			err = spUnsupportedErr.New("views")
		default:
			return true
		}
		return false
	})
	if err != nil {
		return nil, err
	}
	plan.Inspect(proc, func(n sql.Node) bool {
		switch n := n.(type) {
		case *plan.Call:
			if proc.Name == strings.ToLower(n.Name) {
				err = sql.ErrProcedureRecursiveCall.New(proc.Name)
			}
		case *plan.LockTables:  
			err = sql.ErrProcedureInvalidBodyStatement.New("LOCK TABLES")
		case *plan.UnlockTables:  
			err = sql.ErrProcedureInvalidBodyStatement.New("UNLOCK TABLES")
		case *plan.Use:  
			err = sql.ErrProcedureInvalidBodyStatement.New("USE")
		case *plan.LoadData:
			err = sql.ErrProcedureInvalidBodyStatement.New("LOAD DATA")
		default:
			return true
		}
		return false
	})
	if err != nil {
		return nil, err
	}
	return paramNames, nil
}
func resolveProcedureParams(ctx *sql.Context, paramNames map[string]struct{}, proc sql.Node) (sql.Node, error) {
	newProcNode, err := resolveProcedureParamsTransform(ctx, paramNames, proc)
	if err != nil {
		return nil, err
	}
	newProcNode, err = plan.TransformUp(newProcNode, func(n sql.Node) (sql.Node, error) {
		switch n := n.(type) {
		case *plan.InsertInto:
			newSource, err := resolveProcedureParamsTransform(ctx, paramNames, n.Source)
			if err != nil {
				return nil, err
			}
			return n.WithSource(newSource), nil
		case *plan.Union:
			newLeft, err := resolveProcedureParamsTransform(ctx, paramNames, n.Left())
			if err != nil {
				return nil, err
			}
			newRight, err := resolveProcedureParamsTransform(ctx, paramNames, n.Right())
			if err != nil {
				return nil, err
			}
			return n.WithChildren(newLeft, newRight)
		default:
			return n, nil
		}
	})
	if err != nil {
		return nil, err
	}
	newProc, ok := newProcNode.(*plan.Procedure)
	if !ok {
		return nil, fmt.Errorf("expected `*plan.Procedure` but got `%T`", newProcNode)
	}
	return newProc, nil
}
func resolveProcedureParamsTransform(ctx *sql.Context, paramNames map[string]struct{}, n sql.Node) (sql.Node, error) {
	return plan.TransformExpressionsUp(n, func(e sql.Expression) (sql.Expression, error) {
		switch e := e.(type) {
		case *expression.UnresolvedColumn:
			if strings.ToLower(e.Table()) == "" {
				if _, ok := paramNames[strings.ToLower(e.Name())]; ok {
					return expression.NewProcedureParam(e.Name()), nil
				}
			}
			return e, nil
		case *deferredColumn:
			if strings.ToLower(e.Table()) == "" {
				if _, ok := paramNames[strings.ToLower(e.Name())]; ok {
					return expression.NewProcedureParam(e.Name()), nil
				}
			}
			return e, nil
		case *plan.Subquery:  
			newQuery, err := resolveProcedureParamsTransform(ctx, paramNames, e.Query)
			if err != nil {
				return nil, err
			}
			ne := *e
			ne.Query = newQuery
			return &ne, nil
		default:
			return e, nil
		}
	})
}