// Copyright 2023 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package core

import (
	"context"
	"strings"

	"github.com/ngaut/pools"
	"github.com/pingcap/errors"
	"github.com/pingcap/tidb/domain"
	"github.com/pingcap/tidb/kv"
	"github.com/pingcap/tidb/parser/ast"
	"github.com/pingcap/tidb/parser/charset"
	"github.com/pingcap/tidb/parser/model"
	"github.com/pingcap/tidb/parser/mysql"
	"github.com/pingcap/tidb/parser/terror"
	"github.com/pingcap/tidb/sessionctx"
	"github.com/pingcap/tidb/sessionctx/variable"
	"github.com/pingcap/tidb/types"
	driver "github.com/pingcap/tidb/types/parser_driver"
	"github.com/pingcap/tidb/util/chunk"
	"github.com/pingcap/tidb/util/sqlexec"
)

// buildCreateProcedure Generate create stored procedure plan.
func (b *PlanBuilder) buildCreateProcedure(ctx context.Context, node *ast.ProcedureInfo) (Plan, error) {
	p := &CreateProcedure{ProcedureInfo: node, is: b.is}
	procedurceSchema := node.ProcedureName.Schema.O
	if procedurceSchema == "" {
		procedurceSchema = b.ctx.GetSessionVars().CurrentDB
		node.ProcedureName.Schema = model.NewCIStr(b.ctx.GetSessionVars().CurrentDB)
	}
	if procedurceSchema == "" {
		return nil, ErrNoDB
	}
	return p, nil
}

// buildDropProcedure Generate drop stored procedure plan.
func (b *PlanBuilder) buildDropProcedure(ctx context.Context, node *ast.DropProcedureStmt) (Plan, error) {
	p := &DropProcedure{Procedure: node, is: b.is}
	procedurceSchema := node.ProcedureName.Schema.O
	if procedurceSchema == "" {
		procedurceSchema = b.ctx.GetSessionVars().CurrentDB
		node.ProcedureName.Schema = model.NewCIStr(b.ctx.GetSessionVars().CurrentDB)
	}
	if procedurceSchema == "" {
		return nil, ErrNoDB
	}
	return p, nil
}

// ProcedureBaseBody base store procedure structure.
type ProcedureBaseBody interface {
}

// ProcedureSQL store SQL stmt
type ProcedureSQL struct {
	ProcedureBaseBody
	Stmt ast.StmtNode
}

// ProcedureIfBody store if else block
type ProcedureIfBody struct {
	ProcedureBaseBody
	Condition        ast.ExprNode
	ExecutionBranch1 []ProcedureBaseBody
	ExecutionBranch2 ProcedureBaseBody
}

// ProcedureElseBody else block
type ProcedureElseBody struct {
	ProcedureBaseBody
	ExecutionBranch []ProcedureBaseBody
}

// ProcedureWhileBody do while block
type ProcedureWhileBody struct {
	ProcedureBaseBody
	Condition ast.ExprNode
	Body      []ProcedureBaseBody
}

// ProcedureBlock store block structure.
type ProcedureBlock struct {
	ProcedureBaseBody
	Vars               []*ProcedurebodyVal
	Curs               []*ProcedureCur
	ErrorCode          []*ProcedureErrcodeCon
	ErrorCodeState     []*ProcedureErrcodeState
	ErrorStatus        []*ProcedureErrStatusCon
	ProcedureProcStmts []ProcedureBaseBody
}

// ProcedureOpenCur save open cursor command
type ProcedureOpenCur struct {
	ProcedureBaseBody
	CurName string
}

// ProcedureCloseCur save close cursor command
type ProcedureCloseCur struct {
	ProcedureBaseBody
	CurName string
}

// ProcedureFetchInto save fetch into a command
type ProcedureFetchInto struct {
	ProcedureBaseBody
	CurName   string
	Variables []string
}

// ProcedurebodyInfo Store stored procedure content read from the table.
type ProcedurebodyInfo struct {
	Name                string
	Procedurebody       string
	SqlMode             string
	CharacterSetClient  string
	CollationConnection string
	ShemaCollation      string
}

// ProcedurebodyVal Store stored procedure variable.
type ProcedurebodyVal struct {
	DeclNames   []string
	DeclType    *types.FieldType
	DeclDefault ast.ExprNode
	DeclRes     types.Datum
}

// ProcedureCur Store stored procedure variable.
type ProcedureCur struct {
	CurName string
	Curstmt ast.StmtNode
}

// ProcedureErrcodeCon Store stored procedure error code.
type ProcedureErrcodeCon struct {
	Handle  int
	Codes   uint64
	Operate ProcedureBaseBody
}

// ProcedureErrcodeState Store stored procedure error status.
type ProcedureErrcodeState struct {
	Handle    int
	CodeState string
	Operate   ProcedureBaseBody
}

// ProcedureErrStatusCon Store stored procedure variable.
type ProcedureErrStatusCon struct {
	Handle    int
	ErrStatus int
	Operate   ProcedureBaseBody
}

// ProcedureParameterVal Store stored procedure parameter.
type ProcedureParameterVal struct {
	DeclName  string
	DeclType  *types.FieldType
	DeclInput ast.ExprNode
	ParamType int
}

// ProcedurePlan store call plan.
type ProcedurePlan struct {
	IfNotExists    bool
	ProcedureName  *ast.TableName
	ProcedureParam []*ProcedureParameterVal
	Procedurebody  ProcedureBaseBody
	Comment        string
}

// IsSqlstateValid Sanity check for SQLSTATEs.
func IsSqlstateValid(sqlState string) bool {
	if len(sqlState) != 5 {
		return false
	}
	for _, c := range sqlState {
		if (c < '0' || '9' < c) && (c < 'A' || 'Z' < c) {
			return false
		}
	}
	return true
}

// IsSqlStateCompletion check if the specified SQL-state-string defines COMPLETION condition.
func IsSqlStateCompletion(sqlState string) bool {
	return sqlState[0] == '0' && sqlState[1] == '0'
}

// analysiStructure Generate an execution plan based on stored procedure.
func (b *PlanBuilder) analysiStructure(ctx context.Context, stmtNodes []ast.StmtNode, node *ast.CallStmt, collate string) (*ProcedurePlan, error) {
	if len(stmtNodes) > 1 {
		return nil, errors.New("Parse procedure error")
	}

	if len(stmtNodes) == 0 {
		return nil, nil
	}
	switch stmtNodes[0].(type) {
	case *ast.ProcedureInfo:
		plan := &ProcedurePlan{
			IfNotExists:   stmtNodes[0].(*ast.ProcedureInfo).IfNotExists,
			ProcedureName: stmtNodes[0].(*ast.ProcedureInfo).ProcedureName,
		}
		if len(node.Procedure.Args) != len(stmtNodes[0].(*ast.ProcedureInfo).ProcedureParam) {
			err := ErrSpWrongNoOfArgs.GenWithStackByArgs("PROCEDURE", node.Procedure.Schema.String()+"."+
				node.Procedure.FnName.String(), len(stmtNodes[0].(*ast.ProcedureInfo).ProcedureParam),
				len(node.Procedure.Args))
			return nil, err
		}
		params, err := b.buildCallParamPlan(ctx, stmtNodes[0].(*ast.ProcedureInfo), node)
		if err != nil {
			return nil, err
		}
		plan.ProcedureParam = params
		body, err := b.buildCallBodyPlan(ctx, stmtNodes[0].(*ast.ProcedureInfo), collate)
		if err != nil {
			return nil, err
		}
		plan.Procedurebody = body
		return plan, nil
	default:
		return nil, errors.Errorf("Call procedure unsupport node %T", stmtNodes)
	}
}

// getBodyVar Generate block internal variable execution plan.
func (b *PlanBuilder) getBodyVar(ctx context.Context, node *ast.ProcedureBlock, collate string, block *ProcedureBlock) error {
	vars := make([]*ProcedurebodyVal, 0, len(node.ProcedureVars))
	curs := make([]*ProcedureCur, 0, len(node.ProcedureVars))
	errorCode := make([]*ProcedureErrcodeCon, 0, len(node.ProcedureVars))
	errorState := make([]*ProcedureErrcodeState, 0, len(node.ProcedureVars))
	errorStatus := make([]*ProcedureErrStatusCon, 0, len(node.ProcedureVars))
	for _, stmt := range node.ProcedureVars {
		switch x := stmt.(type) {
		case *ast.ProcedureCursor:
			if len(errorCode) >= 1 || len(errorStatus) >= 1 {
				return errors.Trace(ErrSpCursorAfterHandler)
			}
			pvar := &ProcedureCur{
				CurName: x.CurName,
				Curstmt: x.Selectstring,
			}
			curs = append(curs, pvar)
		case *ast.ProcedureDecl:
			// procedure variable must at begin.
			if len(errorCode) >= 1 || len(errorStatus) >= 1 || len(curs) >= 1 {
				return errors.Trace(ErrSpVarcondAfterCurshndlr)
			}
			pvar := &ProcedurebodyVal{
				DeclNames: x.DeclNames,
				DeclType:  x.DeclType,
			}
			err := b.setDefVal(pvar.DeclType, collate)
			if err != nil {
				return err
			}
			if x.DeclDefault != nil {
				res, ok := x.DeclDefault.(*driver.ValueExpr)
				if ok {
					pvar.DeclRes = res.Datum
				}
				pvar.DeclDefault = x.DeclDefault
			}
			vars = append(vars, pvar)
		case *ast.ProcedureErrorControl:
			// procedure error control must at end.
			operate, err := b.buildProcedureNode(ctx, x.Operate, collate)
			if err != nil {
				return err
			}
			for _, errorInfo := range x.ErrorCon {
				switch v := errorInfo.(type) {
				case *ast.ProcedureErrorVal:
					errCon := &ProcedureErrcodeCon{
						Handle:  x.ControlHandle,
						Codes:   v.ErrorNum,
						Operate: operate,
					}
					errorCode = append(errorCode, errCon)
				case *ast.ProcedureErrorCon:
					errCon := &ProcedureErrStatusCon{
						Handle:    x.ControlHandle,
						ErrStatus: v.ErrorCon,
						Operate:   operate,
					}
					errorStatus = append(errorStatus, errCon)
				case *ast.ProcedureErrorState:
					if !IsSqlstateValid(v.CodeStatus) || IsSqlStateCompletion(v.CodeStatus) {
						return ErrSpBadSQLstate.GenWithStackByArgs(v.CodeStatus)
					}
					errCon := &ProcedureErrcodeState{
						Handle:    x.ControlHandle,
						CodeState: v.CodeStatus,
						Operate:   operate,
					}
					errorState = append(errorState, errCon)
				default:
					return errors.Errorf("Error control unsupport node %T", errorInfo)
				}
			}

		default:
			return errors.Errorf("Call procedure unsupport node %T", stmt)
		}
	}
	block.Vars = vars
	block.Curs = curs
	block.ErrorCode = errorCode
	block.ErrorCodeState = errorState
	block.ErrorStatus = errorStatus
	return nil
}

// buildProcedureNode Analyze stored procedure internal objects.
func (b *PlanBuilder) buildProcedureNode(ctx context.Context, node ast.StmtNode, collate string) (basebody ProcedureBaseBody, err error) {
	switch x := node.(type) {
	case *ast.ProcedureBlock:
		block := &ProcedureBlock{}
		basebody := make([]ProcedureBaseBody, 0, len(x.ProcedureProcStmts))
		err := b.getBodyVar(ctx, x, collate, block)
		if err != nil {
			return nil, err
		}
		for _, stmt := range node.(*ast.ProcedureBlock).ProcedureProcStmts {
			body, err := b.buildProcedureNode(ctx, stmt, collate)
			if err != nil {
				return nil, err
			}
			basebody = append(basebody, body)
		}
		block.ProcedureProcStmts = basebody
		return block, nil
	case *ast.ProcedureIfInfo:
		body, err := b.buildProcedureNode(ctx, x.IfBody, collate)
		if err != nil {
			return nil, err
		}
		return body, nil
	case *ast.ProcedureIfBlock:
		block := &ProcedureIfBody{}
		block.ExecutionBranch1 = make([]ProcedureBaseBody, 0, len(x.ProcedureIfStmts))
		block.Condition = x.IfExpr
		for _, stmt := range x.ProcedureIfStmts {
			body, err := b.buildProcedureNode(ctx, stmt, collate)
			if err != nil {
				return nil, err
			}
			block.ExecutionBranch1 = append(block.ExecutionBranch1, body)
		}
		if x.ProcedureElseStmt != nil {
			body, err := b.buildProcedureNode(ctx, x.ProcedureElseStmt, collate)
			if err != nil {
				return nil, err
			}
			block.ExecutionBranch2 = body
		}
		return block, nil
	case *ast.ProcedureElseBlock:
		block := &ProcedureElseBody{}
		block.ExecutionBranch = make([]ProcedureBaseBody, 0, len(x.ProcedureIfStmts))
		for _, stmt := range x.ProcedureIfStmts {
			body, err := b.buildProcedureNode(ctx, stmt, collate)
			if err != nil {
				return nil, err
			}
			block.ExecutionBranch = append(block.ExecutionBranch, body)
		}
		return block, nil
	case *ast.ProcedureElseIfBlock:
		body, err := b.buildProcedureNode(ctx, x.ProcedureIfStmt, collate)
		if err != nil {
			return nil, err
		}
		return body, nil
	// to do: Add logical judgment and other operations.
	case *ast.ProcedureWhileStmt:
		block := &ProcedureWhileBody{}
		block.Body = make([]ProcedureBaseBody, 0, len(x.Body))
		block.Condition = x.Condition
		for _, stmt := range x.Body {
			body, err := b.buildProcedureNode(ctx, stmt, collate)
			if err != nil {
				return nil, err
			}
			block.Body = append(block.Body, body)
		}
		return block, nil
	case *ast.ProcedureOpenCur:
		block := &ProcedureOpenCur{}
		block.CurName = x.CurName
		return block, nil
	case *ast.ProcedureCloseCur:
		block := &ProcedureCloseCur{}
		block.CurName = x.CurName
		return block, nil
	case *ast.ProcedureFetchInto:
		block := &ProcedureFetchInto{}
		block.CurName = x.CurName
		block.Variables = x.Variables
		return block, nil
	default:
		node := &ProcedureSQL{Stmt: node}
		return node, nil
	}
}

// buildCallParamPlan Handle stored procedure parameters.
func (b *PlanBuilder) buildCallParamPlan(ctx context.Context, stmtNodes *ast.ProcedureInfo, node *ast.CallStmt) ([]*ProcedureParameterVal, error) {
	params := make([]*ProcedureParameterVal, 0, len(stmtNodes.ProcedureParam))
	for i, stmt := range stmtNodes.ProcedureParam {
		param := &ProcedureParameterVal{
			DeclName:  stmt.ParamName,
			DeclType:  stmt.ParamType,
			ParamType: stmt.Paramstatus,
		}
		param.DeclInput = node.Procedure.Args[i]
		params = append(params, param)
	}
	return params, nil
}

// buildCallBodyPlan Generate an execution plan based on the stored procedure structure.
func (b *PlanBuilder) buildCallBodyPlan(ctx context.Context, stmtNodes *ast.ProcedureInfo, collate string) (basebody ProcedureBaseBody, err error) {
	return b.buildProcedureNode(ctx, stmtNodes.ProcedureBody, collate)
}

// buildCallProcedure Generate call command execution plan.
func (b *PlanBuilder) buildCallProcedure(ctx context.Context, node *ast.CallStmt) (outplan Plan, err error) {
	p := &CallStmt{Callstmt: node, Is: b.is}
	// get database name.
	procedurceSchema := node.Procedure.Schema.O
	if procedurceSchema == "" {
		procedurceSchema = b.ctx.GetSessionVars().CurrentDB
		node.Procedure.Schema = model.NewCIStr(b.ctx.GetSessionVars().CurrentDB)
	}
	if procedurceSchema == "" {
		return nil, ErrNoDB
	}
	// get stored procedure name.
	procedurceName := node.Procedure.FnName.String()
	// Check if database exists
	_, ok := b.is.SchemaByName(node.Procedure.Schema)
	if !ok {
		return nil, ErrBadDB.GenWithStackByArgs(procedurceSchema)
	}

	// get stored procedure structure.
	procedureInfo, err := b.fetchProcdureInfo(procedurceName, procedurceSchema)
	if err != nil {
		return nil, err
	}
	sqlModeSave, ok := b.ctx.GetSessionVars().GetSystemVar(variable.SQLModeVar)
	if !ok {
		return nil, errors.New("can not find sql_mode")
	}
	err = b.ctx.GetSessionVars().SetSystemVar(variable.SQLModeVar, procedureInfo.SqlMode)
	if err != nil {
		_ = b.ctx.GetSessionVars().SetSystemVar(variable.SQLModeVar, sqlModeSave)
		return nil, err
	}

	p.OldSqlMod = sqlModeSave
	b.ctx.GetSessionVars().SetInCallProcedure()
	b.ctx.GetSessionVars().NewProcedureVariables()
	defer func() {
		if err != nil {
			b.ctx.GetSessionVars().OutCallProcedure()
			b.ctx.GetSessionVars().ClearProcedureVariable()
		}
	}()
	stmtNodes, err := b.ctx.GetSessionExec().SqlParse(ctx, procedureInfo.Procedurebody)
	if err != nil {
		_ = b.ctx.GetSessionVars().SetSystemVar(variable.SQLModeVar, sqlModeSave)
		return nil, err
	}
	plan, err := b.analysiStructure(ctx, stmtNodes, node, procedureInfo.CollationConnection)
	if err != nil {
		_ = b.ctx.GetSessionVars().SetSystemVar(variable.SQLModeVar, sqlModeSave)
		return nil, err
	}
	p.Plan = plan
	b.ctx.GetSessionVars().ClearProcedureVariable()
	b.ctx.GetSessionVars().NewProcedureVariables()
	return p, nil
}

// fetchProcdureInfo read the system table to get the stored procedure structure.
func (b *PlanBuilder) fetchProcdureInfo(name, db string) (*ProcedurebodyInfo, error) {
	ctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnBindInfo)
	restrictedCtx, err := b.getSysSession()
	if err != nil {
		return nil, err
	}
	defer b.releaseSysSession(ctx, restrictedCtx)
	exec := restrictedCtx.(sqlexec.SQLExecutor)
	sql := new(strings.Builder)
	//names = []string{"Procedure", "sql_mode", "Create Procedure", "character_set_client", "collation_connection", "Database Collation"}
	sqlexec.MustFormatSQL(sql, "select name, sql_mode ,definition_utf8,parameter_str,character_set_client, connection_collation,")
	sqlexec.MustFormatSQL(sql, "schema_collation from %n.%n where route_schema = %?  and name = %? and type = 'PROCEDURE' ", mysql.SystemDB, mysql.Routines, db, name)
	rs, err := exec.ExecuteInternal(ctx, sql.String())
	if rs == nil {
		return nil, ErrSpDoesNotExist.GenWithStackByArgs("PROCEDURE")
	}
	if err != nil {
		return nil, err
	}

	var rows []chunk.Row
	defer terror.Call(rs.Close)
	if rows, err = sqlexec.DrainRecordSet(ctx, rs, 8); err != nil {
		return nil, err
	}
	if len(rows) == 0 {
		return nil, ErrSpDoesNotExist.GenWithStackByArgs("PROCEDURE", name)
	}
	if len(rows) != 1 {
		return nil, errors.New("Multiple stored procedures found in table " + mysql.Routines)
	}
	procedurebodyInfo := &ProcedurebodyInfo{}
	procedurebodyInfo.Name = rows[0].GetString(0)
	procedurebodyInfo.Procedurebody = " CREATE PROCEDURE " + rows[0].GetString(0) + "(" + rows[0].GetString(3) + ") \n" + rows[0].GetString(2)
	procedurebodyInfo.SqlMode = rows[0].GetSet(1).String()
	procedurebodyInfo.CharacterSetClient = rows[0].GetString(4)
	procedurebodyInfo.CollationConnection = rows[0].GetString(5)
	procedurebodyInfo.ShemaCollation = rows[0].GetString(6)
	return procedurebodyInfo, nil
}

// setDefVal set FieldType default len.
func (b *PlanBuilder) setDefVal(tp *types.FieldType, collate string) error {
	if typesNeedCharset(tp.GetType()) {
		err := b.setFieldCharset(tp, collate)
		if err != nil {
			return err
		}
	} else {
		tp.SetCharset(charset.CharsetBin)
		tp.SetCollate(charset.CharsetBin)
	}
	if tp.GetFlen() != types.UnspecifiedLength || tp.GetDecimal() != types.UnspecifiedLength {
		return nil
	}
	defaultFlen, defaultDecimal := mysql.GetDefaultFieldLengthAndDecimal(tp.GetType())
	switch tp.GetType() {
	case mysql.TypeTimestamp, mysql.TypeDatetime, mysql.TypeDuration:
		tp.SetFlen(defaultFlen)
	case mysql.TypeDouble, mysql.TypeFloat:
		tp.SetFlen(defaultFlen)
		tp.SetDecimal(defaultDecimal)

	case mysql.TypeNewDecimal:
		tp.SetFlen(defaultFlen)
		tp.SetDecimal(defaultDecimal)

	case mysql.TypeBit, mysql.TypeVarchar, mysql.TypeString, mysql.TypeVarString:
		tp.SetFlen(defaultFlen)
	case mysql.TypeTiny, mysql.TypeShort, mysql.TypeInt24, mysql.TypeLong, mysql.TypeLonglong:
		tp.SetFlen(defaultFlen)

	case mysql.TypeYear:
		tp.SetFlen(defaultFlen)
	}

	return nil
}

// typesNeedCharset check if tp need charset.
func typesNeedCharset(tp byte) bool {
	switch tp {
	case mysql.TypeString, mysql.TypeVarchar, mysql.TypeVarString,
		mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob,
		mysql.TypeEnum, mysql.TypeSet:
		return true
	}
	return false
}

// setFieldCharset set charset.
func (b *PlanBuilder) setFieldCharset(tp *types.FieldType, collate string) error {
	if tp.GetCharset() == "" && tp.GetCollate() == "" {
		tp.SetCollate(collate)
		cs, err := charset.GetCollationByName(tp.GetCollate())
		if err != nil {
			return err
		}
		tp.SetCharset(cs.CharsetName)
		return nil
	}
	if tp.GetCharset() != "" {
		collate, err := charset.GetDefaultCollation(tp.GetCharset())
		if err != nil {
			return err
		}
		tp.SetCollate(collate)
		return nil
	} else {
		cs, err := charset.GetCollationByName(tp.GetCharset())
		if err != nil {
			return err
		}
		tp.SetCharset(cs.CharsetName)
	}
	return nil
}

// NewEmptyVars create procedure variable with NULL value.
func NewEmptyVars(ctx context.Context, name string, tp *types.FieldType, sessionVars *variable.SessionVars) error {
	datum := types.NewDatum(0)
	datum.SetNull()
	varVar, err := datum.Clone().ConvertTo(sessionVars.StmtCtx, tp)
	if err != nil {
		return err
	}
	err = sessionVars.NewProcedureVariable(name, varVar, tp)
	if err != nil {
		return err
	}
	return nil
}

// getSysSession get sySsession pool.
func (b *PlanBuilder) getSysSession() (sessionctx.Context, error) {
	dom := domain.GetDomain(b.ctx)
	sysSessionPool := dom.SysSessionPool()
	ctx, err := sysSessionPool.Get()
	if err != nil {
		return nil, err
	}
	restrictedCtx := ctx.(sessionctx.Context)
	restrictedCtx.GetSessionVars().InRestrictedSQL = true
	return restrictedCtx, nil
}

// releaseSysSession release sySsession pool.
func (b *PlanBuilder) releaseSysSession(ctx context.Context, sctx sessionctx.Context) {
	if sctx == nil {
		return
	}
	dom := domain.GetDomain(b.ctx)
	sysSessionPool := dom.SysSessionPool()
	if _, err := sctx.(sqlexec.SQLExecutor).ExecuteInternal(ctx, "rollback"); err != nil {
		sctx.(pools.Resource).Close()
		return
	}
	sysSessionPool.Put(sctx.(pools.Resource))
}

// buildCallProcedure Generate call command execution plan.
func (b *PlanBuilder) buildAlterProcedure(ctx context.Context, node *ast.AlterProcedureStmt) (outplan Plan, err error) {
	p := &AlterProcedure{Procedure: node, is: b.is}
	// get database name.
	procedurceSchema := node.ProcedureName.Schema.O
	if procedurceSchema == "" {
		procedurceSchema = b.ctx.GetSessionVars().CurrentDB
		node.ProcedureName.Schema = model.NewCIStr(b.ctx.GetSessionVars().CurrentDB)
	}
	if procedurceSchema == "" {
		return nil, ErrNoDB
	}
	// Check if database exists
	_, ok := b.is.SchemaByName(node.ProcedureName.Schema)
	if !ok {
		return nil, ErrBadDB.GenWithStackByArgs(procedurceSchema)
	}
	return p, nil
}
