关于tidb:技术分享-使用-TiDB-的-SQL-解析器生成-SQL-指纹

60次阅读

共计 2353 个字符,预计需要花费 6 分钟才能阅读完成。

作者:孙健

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

本文起源:原创投稿

* 爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。


本文次要介绍如何借助 TiDB SQL 解析自定义生成 SQL 指纹,采纳了一种有别于 pt-fingerprint(https://www.percona.com/doc/p… 的形式。

什么是 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 语法树的构造。
正文完
 0