parser 解析 function 请教

为提高效率,请提供以下信息,问题描述清晰能够更快得到解决:

【概述】 场景 + 问题概述
我尝试在对 parser 进行修改,希望其能支持对 function 的解析,在解析部分 sql 的时候有些疑惑,请各位大佬指教。

【parser 版本】
v4.0.2

【步骤】

/********************************************************************
* Create Function Statement
 *
 * CREATE
 *     [DEFINER = user]
 *     FUNCTION sp_name ([func_parameter[,...]])
 *     RETURNS type
 *     [characteristic ...] routine_body
 *
 * func_parameter:
 *     param_name type
 *
 * type:
 *     Any valid MySQL data type
 *
 * characteristic: {
 *     COMMENT 'string'
 *   | LANGUAGE SQL
 *   | [NOT] DETERMINISTIC
 *   | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
 *   | SQL SECURITY { DEFINER | INVOKER }
 * }
 *
 * routine_body:
 *     Valid SQL routine statement
 *
 * Ref:
 *    https://dev.mysql.com/doc/refman/8.0/en/create-procedure.html
 *******************************************************************/
CreateFunctionStmt:
	"CREATE" "FUNCTION" FunctionName FunctionParameterOpt ReturnDataOpt CharacteristicOptionList
	{
		x := &ast.CreateFunctionStmt{
			Name: $3.(*ast.TableName),
			Args: $4.([]*ast.ColumnDef),
			Rtp:  $5.(*types.FieldType),
		}
		if $6 != nil {
			x.Characteristic = $6.(*ast.CharacteristicOption)
		}
		$$ = x
	}

CharacteristicOptionList:
	/* empty */
	{
		$$ = nil
	}
|	CharacteristicOptionList CharacteristicOption
	{
		// Merge the options
		if $1 == nil {
			$$ = $2
		} else {
			opt1 := $1.(*ast.CharacteristicOption)
			opt2 := $1.(*ast.CharacteristicOption)
			if len(opt2.Comment) > 0 {
				opt1.Comment = opt2.Comment
			} else if opt2.Deterministic != opt1.Deterministic {
				opt1.Deterministic = opt2.Deterministic
			} else if opt2.Security != opt1.Security {
				opt1.Security = opt2.Security
			}
			$$ = opt1
		}
	}

CharacteristicOption:
	{
		$$ = nil
	}
|	"COMMENT" stringLit
	{
		$$ = &ast.CharacteristicOption{
			Comment: $2,
		}
	}
|	DeterministicOrNotOp
	{
		$$ = &ast.CharacteristicOption{
			Deterministic: $1.(bool),
		}
	}
|	ViewSQLSecurity
	{
		$$ = &ast.CharacteristicOption{
			Security: $1.(model.ViewSecurity),
		}
	}

DeterministicOrNotOp:
	"DETERMINISTIC"
	{
		$$ = true
	}
|	"NOT" "DETERMINISTIC"
	{
		$$ = false
	}

FunctionName:
	TableName

FunctionParameterOpt:
	/* empty */
	{
		$$ = make([]*ast.ColumnDef, 0, 1)
	}
|	'(' FunctionColumnDefList ')'
	{
		$$ = $2.([]*ast.ColumnDef)
	}

FunctionColumnDefList:
	FunctionColumnDef
	{
		$$ = []*ast.ColumnDef{$1.(*ast.ColumnDef)}
	}
|	FunctionColumnDefList ',' FunctionColumnDef
	{
		$$ = append($1.([]*ast.ColumnDef), $3.(*ast.ColumnDef))
	}

FunctionColumnDef:
	ColumnName Type
	{
		colDef := &ast.ColumnDef{Name: $1.(*ast.ColumnName), Tp: $2.(*types.FieldType)}
		$$ = colDef
	}

ReturnDataOpt:
	"RETURNS" Type
	{
		$$ = $2.(*types.FieldType)
	}

ddl.go 中代码如下

// CreateFunctionStmt is a statement to create a Function.
type CreateFunctionStmt struct {
	ddlNode

	IfNotExists bool
	Name        *TableName
	Args        []*ColumnDef
	Rtp         *types.FieldType

	Characteristic *CharacteristicOption
}

// Restore implements Node interface.
func (n *CreateFunctionStmt) Restore(ctx *format.RestoreCtx) error {
	ctx.WriteKeyWord("CREATE ")
	ctx.WriteKeyWord("FUNCTION ")
	if n.IfNotExists {
		ctx.WriteKeyWord("IF NOT EXISTS ")
	}
	if err := n.Name.Restore(ctx); err != nil {
		return errors.Annotate(err, "An error occurred while create CreateFunctionStmt.Name")
	}
	if len(n.Args) > 0 {
		ctx.WritePlain("(")
		for i, col := range n.Args {
			if i > 0 {
				ctx.WritePlain(",")
			}
			if err := col.Restore(ctx); err != nil {
				return errors.Annotatef(err, "An error occurred while splicing CreateFunctionStmt Args: [%v]", i)
			}
		}

		ctx.WritePlain(")")
	}
	ctx.WritePlain(" RETURNS ")
	if err := n.Rtp.Restore(ctx); err != nil {
		return errors.Annotatef(err, "An error occurred while splicing CreateFunctionStmt Rtp: [%v]", n.Rtp)
	}
	if n.Characteristic != nil {
		ctx.WritePlain(" ")
		if err := n.Characteristic.Restore(ctx); err != nil {
			return errors.Annotatef(err, "An error occurred while splicing CreateFunctionStmt Characteristic: [%v]", n.Characteristic)
		}
	}

	return nil
}

// Accept implements Node Accept interface.
func (n *CreateFunctionStmt) Accept(v Visitor) (Node, bool) {
	newNode, skipChildren := v.Enter(n)
	if skipChildren {
		return v.Leave(newNode)
	}
	n = newNode.(*CreateFunctionStmt)
	node, ok := n.Name.Accept(v)
	if !ok {
		return n, false
	}
	n.Name = node.(*TableName)
	return v.Leave(n)
}

// CharacteristicOption ...
// CharacteristicOption for function or procedure
type CharacteristicOption struct {
	node

	Comment       string
	Deterministic bool
	Security      model.ViewSecurity
}

// Accept implements Node Accept interface.
// Accept implements Node Accept interface.
func (n *CharacteristicOption) Accept(v Visitor) (Node, bool) {
	newNode, skipChildren := v.Enter(n)
	if skipChildren {
		return v.Leave(newNode)
	}
	n = newNode.(*CharacteristicOption)
	return v.Leave(n)
}

// Restore implements Node interface.
func (n *CharacteristicOption) Restore(ctx *format.RestoreCtx) error {
	hasPrevOption := false
	if n.Comment != "" {
		if hasPrevOption {
			ctx.WritePlain(" ")
		}
		ctx.WriteKeyWord("COMMENT ")
		ctx.WriteString(n.Comment)
		hasPrevOption = true
	}
	if n.Deterministic {
		if hasPrevOption {
			ctx.WritePlain(" ")
		}
		ctx.WriteKeyWord("DETERMINISTIC")
		hasPrevOption = true
	} else {
		if hasPrevOption {
			ctx.WritePlain(" ")
		}
		ctx.WriteKeyWord("NOT DETERMINISTIC")
		hasPrevOption = true
	}

	ctx.WriteKeyWord(" SQL SECURITY ")
	ctx.WriteKeyWord(n.Security.String())

	return nil
}

在执行 make 的时候,出现了

make
gofmt (simplify)
bin/goyacc -o parser.go -p yy -t Parser parser.y
conflicts: 4 shift/reduce
conflicts: 6 reduce/reduce

请问,我该怎么处理 CharacteristicOptionList 这个值?

TiDB 开发者社区小伙伴已经在看了,请稍等哈

一般 shift/reduce reduce/reduce 这种问题都是由于语法定义里面有冲突导致的

有 github 上的代码么?我需要把 parser 的改动拉下来看一下

好的,稍后我把代码放出来。

https://github.com/recall704/parser 对应 branch 为 function (https://github.com/recall704/parser/tree/function)

不对呀,function 这个branch 相对于 pingcap/master 没有改动呀?

sorry,现在有了

https://github.com/recall704/parser/blob/317532f90ec140b8fcac43043fb7eec145c24527/parser.y#L13197

你可以把 list 改写成这种形式:

CharacteristicOptionList:
CharacteristicOption
| CharacteristicOptionList CharacteristicOption

list 就是1个或者多个,不能够为空

再然后把 CharacteristicOption 改掉,不要让它跟 ViewSQLSecurity 同时可以以 empty 开头,这样 yacc 语法没法决定使用哪一条规则,会出现 reduce / shift 那个报错

尽量让每个规则都很明确,比如

List
Item
| List Item

list 为 1个或者多个元素,而不要搞零个元素的,然后如果是可以为零个的,就加另一条规则

ListOpt
{}
| List

我现在的疑惑在于

  • CharacteristicOptionList 整个为 empty 的时候,我的 empty 应该在下面的每个子项中处理,还是应该是 empty | CharacteristicOptionList
  • 在每一个子项中,都有可能是 empty,多个 empty 同时出现就有可能出现 shift/reduce 错误,多个 empty 应该如何处理

你要理解 shift/reduce 发生的原因,是 yacc 不知道使用哪一条规则了,语法部分定义的有歧义

我的 empty 应该在下面的每个子项中处理,还是应该是 empty | CharacteristicOptionList

语法书写者不知道放到上层处理,还是子项处理的时候,那 yacc 工具更不会知道。
你可以随便怎么弄,让它没有歧义就行了。

这里有一个例子, https://github.com/recall704/parser/pull/3

之间你的代码里面的歧义,主要是 CharacteristicOptionList 可以为空,而子项 ViewSQLSecurity 那一条也是一个可以为空的,于是工具就不知道用哪条规则了。我改了一下让 ViewSQLSecurity 子项不会为空,就可以了。

1赞

好的,非常感谢。

我先尝试一下。