技术分享 | 使用 TiDB 的 SQL 解析器生成 SQL 指纹

作者:孙健

爱可生研发工程师,负责高可用组建和 SQL 审核相关开发。

本文来源:原创投稿

*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。


本文主要介绍如何借助 TiDB SQL 解析自定义生成 SQL 指纹,采用了一种有别于 pt-fingerprint(https://www.percona.com/doc/percona-toolkit/3.0/pt-fingerprint.html) 的方式。

什么是 SQL指纹

SQL 指纹指将一条 SQL 中的字面值替换成其他固定符号。可以用来做 SQL 脱敏或者 SQL 归类。
例如:

select * from t1 where id = 100;

转换成:

select * from t1 where id = ?;

pt-fingerprint 的实现

pt-fingerprint 的代码实现看,它主要是通过正则匹配 SQL 字符串来替换对应字符。代码有 2 千多行,完全通过字符串解析会使得代码及其复杂而难以阅读,好处是无需关心 SQL 语义。

基于 TiDB SQL parser 的实现

TiDB SQL parser 的功能是把 SQL 语句按照 SQL 语法规则进行解析,将文本转换成抽象语法树,另外 TiDB SQL parser 支持将语法树转换成 SQL文本,因此可以通过修改语法树结构达到修改 SQL 文本的目的。

1. 通过 TiDB SQL 解析器将 SQL 解析成语法树

解析出的语法树大致如下,其中"…" 代表之前存在多级。

&ast.SelectStmt {
    Fields:
        ... &ast.WildCard
    From: 
        ... &ast.TableName
            ... "t1"
    Where: &ast.BinaryOperationExpr
        L: &ast.ColumnNameExpr
            ... "id"
        R:&ast.ValueExpr
            ... 100
}                

2. 修改语法树上节点对应的值

TiDB 语法解析器代码实现了一套访问者的设计模式,可以通过实现一个Visitor 来遍历语法树。按照1中的语法树结构,我们只需要在遍历到ast.ValueExpr对象时将他的具体数值替换成?

Visitor 接口:

// Visitor visits a Node.
type Visitor interface {
	Enter(n Node) (node Node, skipChildren bool)
	Leave(n Node) (node Node, ok bool)
}

实现 Visitor 接口:

//此处省略N行代码

// 定义一个 FingerprintVisitor 使其实现 Visitor 接口
type FingerprintVisitor struct{}

func (f *FingerprintVisitor) Enter(n ast.Node) (node ast.Node, skipChildren bool) {
    // 当访问到ValueExpr 时,只需要将ValueExpr的值替换掉就行
	if v, ok := n.(*driver.ValueExpr); ok {
		v.Type.Charset = ""
		v.SetValue([]byte("?"))
	}
	return n, false
}

func (f *FingerprintVisitor) Leave(n ast.Node) (node ast.Node, ok bool) {
	return n, true
}

3. 将语法树还原成 SQL

TiDB SQL parser 从 v3 版本开始提供接口Restore(ctx *RestoreCtx) error 支持将语法树转化成 SQL 文本

完整代码

package main

import (
	"bytes"
	"fmt"

	"github.com/pingcap/parser"
	"github.com/pingcap/parser/ast"
	"github.com/pingcap/parser/format"
	driver "github.com/pingcap/tidb/types/parser_driver"
)

// 定义一个 FingerprintVisitor 使其实现 Visitor 接口
type FingerprintVisitor struct{}

func (f *FingerprintVisitor) Enter(n ast.Node) (node ast.Node, skipChildren bool) {
	// 当访问到ValueExpr 时,只需要将ValueExpr的值替换掉就行
	if v, ok := n.(*driver.ValueExpr); ok {
		v.Type.Charset = ""
		v.SetValue([]byte("?"))
	}
	return n, false
}

func (f *FingerprintVisitor) Leave(n ast.Node) (node ast.Node, ok bool) {
	return n, true
}

func main() {
	sql := "select * from t1 where id = 100;"
	p := parser.New()
	stmt, err := p.ParseOneStmt(sql, "", "")
	if err != nil {
		// 省略错误处理
		return
	}
	stmt.Accept(&FingerprintVisitor{})

	buf := new(bytes.Buffer)
	restoreCtx := format.NewRestoreCtx(format.RestoreKeyWordUppercase|format.RestoreNameBackQuotes, buf)
	err = stmt.Restore(restoreCtx)
	if nil != err {
		// 省略错误处理
		return
	}
	fmt.Println(buf.String())
	// SELECT * FROM `t1` WHERE `id`=?
}

总结

  1. 使用 TiDB SQL parser 可以快速准确的实现 SQL 指纹,相比字符串解析降低了阅读的复杂度;
  2. 额外的你需要花时间了解 TiDB 语法树的结构。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值