tidb sql parser判断sql是否只读

tidb需要解析sql,生成执行计划,用到parser工具 https://github.com/pingcap/parser

一般上,SQL 语言通常按照功能划分成以下的 4 个部分:

  • DDL (Data Definition Language):数据定义语言,用来定义数据库对象,包括库、表、视图和索引等。
  • DML (Data Manipulation Language):数据操作语言,用来操作和业务相关的记录。
  • DQL (Data Query Language):数据查询语言,用来查询经过条件筛选的记录。
  • DCL (Data Control Language):数据控制语言,用来定义访问权限和安全级别。

常见的是ddl与dml。

背景

之前遇到一个数据服务,上面维护了大量的数据库凭证信息,而且产品或者开发人员,可以直接触发sql查询数据。这些数据库凭证信息,有些是只读权限,但有些是有写权限的。所以,在触发sql前,需要检查sql是否会动用到写权限,如果是,则拦截掉。当然,很多时候sql复杂度高的时候,就很难通过正则的方式来判断sql是否包含只读操作,

sql可以通过parser工具,解析成抽象语法树(Abstract Syntax Tree,AST)。然后再通过遍历ast的Node,判断里面每个操作是否只读,就可以完成这个需求。

从简单sql看背后的数据结构

代码就不贴了,这里用到spew包来跟踪结构体
这是 SELECT a, b FROM t 解析出来的数据

(*ast.SelectStmt)(0xc0001584e0)({
 dmlNode: (ast.dmlNode) {
  stmtNode: (ast.stmtNode) {
   node: (ast.node) {
    text: (string) (len=18) "SELECT a, b FROM t"
   }
  }
 },
 resultSetNode: (ast.resultSetNode) {
  resultFields: ([]*ast.ResultField) <nil>
 },
 SelectStmtOpts: (*ast.SelectStmtOpts)(0xc00009dbf0)({
  Distinct: (bool) false,
  SQLBigResult: (bool) false,
  SQLBufferResult: (bool) false,
  SQLCache: (bool) true,
  SQLSmallResult: (bool) false,
  CalcFoundRows: (bool) false,
  StraightJoin: (bool) false,
  Priority: (mysql.PriorityEnum) 0,
  TableHints: ([]*ast.TableOptimizerHint) <nil>
 }),
 Distinct: (bool) false,
 From: (*ast.TableRefsClause)(0xc00000ea00)({
  node: (ast.node) {
   text: (string) ""
  },
  TableRefs: (*ast.Join)(0xc0000f4280)({
   node: (ast.node) {
    text: (string) ""
   },
   resultSetNode: (ast.resultSetNode) {
    resultFields: ([]*ast.ResultField) <nil>
   },
   Left: (*ast.TableSource)(0xc0000306c0)({
    node: (ast.node) {
     text: (string) ""
    },
    Source: (*ast.TableName)(0xc0000ce370)({
     node: (ast.node) {
      text: (string) ""
     },
     resultSetNode: (ast.resultSetNode) {
      resultFields: ([]*ast.ResultField) <nil>
     },
     Schema: (model.CIStr) ,
     Name: (model.CIStr) t,
     DBInfo: (*model.DBInfo)(<nil>),
     TableInfo: (*model.TableInfo)(<nil>),
     IndexHints: ([]*ast.IndexHint) {
     },
     PartitionNames: ([]model.CIStr) {
     }
    }),
    AsName: (model.CIStr)
   }),
   Right: (ast.ResultSetNode) <nil>,
   Tp: (ast.JoinType) 0,
   On: (*ast.OnCondition)(<nil>),
   Using: ([]*ast.ColumnName) <nil>,
   NaturalJoin: (bool) false,
   StraightJoin: (bool) false
  })
 }),
 Where: (ast.ExprNode) <nil>,
 Fields: (*ast.FieldList)(0xc00009dc20)({
  node: (ast.node) {
   text: (string) ""
  },
  Fields: ([]*ast.SelectField) (len=2 cap=2) {
   (*ast.SelectField)(0xc00007b5c0)({
    node: (ast.node) {
     text: (string) (len=1) "a"
    },
    Offset: (int) 7,
    WildCard: (*ast.WildCardField)(<nil>),
    Expr: (*ast.ColumnNameExpr)(0xc0000f4180)({
     exprNode: (ast.exprNode) {
      node: (ast.node) {
       text: (string) ""
      },
      Type: (types.FieldType) unspecified,
      flag: (uint64) 8
     },
     Name: (*ast.ColumnName)(0xc0001890a0)(a),
     Refer: (*ast.ResultField)(<nil>)
    }),
    AsName: (model.CIStr) ,
    Auxiliary: (bool) false
   }),
   (*ast.SelectField)(0xc00007b620)({
    node: (ast.node) {
     text: (string) (len=1) "b"
    },
    Offset: (int) 10,
    WildCard: (*ast.WildCardField)(<nil>),
    Expr: (*ast.ColumnNameExpr)(0xc0000f4200)({
     exprNode: (ast.exprNode) {
      node: (ast.node) {
       text: (string) ""
      },
      Type: (types.FieldType) unspecified,
      flag: (uint64) 8
     },
     Name: (*ast.ColumnName)(0xc000189110)(b),
     Refer: (*ast.ResultField)(<nil>)
    }),
    AsName: (model.CIStr) ,
    Auxiliary: (bool) false
   })
  }
 }),
 GroupBy: (*ast.GroupByClause)(<nil>),
 Having: (*ast.HavingClause)(<nil>),
 WindowSpecs: ([]ast.WindowSpec) <nil>,
 OrderBy: (*ast.OrderByClause)(<nil>),
 Limit: (*ast.Limit)(<nil>),
 LockTp: (ast.SelectLockType) none,
 TableHints: ([]*ast.TableOptimizerHint) <nil>,
 AfterSetOperator: (*ast.SetOprType)(<nil>),
 IsInBraces: (bool) false,
 QueryBlockOffset: (int) 0,
 SelectIntoOpt: (*ast.SelectIntoOption)(<nil>)
})

可以看到是这个sql解析只有一个SelectStmt的操作。其它字段的功能可以查看源码了解。

其它stmt

可以参考
https://github.com/pingcap/parser/blob/v4.0.2/ast/ddl.go

	_ DDLNode = &AlterTableStmt{}
	_ DDLNode = &CreateDatabaseStmt{}
	_ DDLNode = &CreateIndexStmt{}
	_ DDLNode = &CreateTableStmt{}
	_ DDLNode = &CreateViewStmt{}
	_ DDLNode = &CreateSequenceStmt{}
	_ DDLNode = &DropDatabaseStmt{}
	_ DDLNode = &DropIndexStmt{}
	_ DDLNode = &DropTableStmt{}
	_ DDLNode = &DropSequenceStmt{}
	_ DDLNode = &RenameTableStmt{}
	_ DDLNode = &TruncateTableStmt{}
	_ DDLNode = &RepairTableStmt{}

https://github.com/pingcap/parser/blob/v4.0.2/ast/dml.go

	_ DMLNode = &DeleteStmt{}
	_ DMLNode = &InsertStmt{}
	_ DMLNode = &SetOprStmt{}
	_ DMLNode = &UpdateStmt{}
	_ DMLNode = &SelectStmt{}
	_ DMLNode = &ShowStmt{}
	_ DMLNode = &LoadDataStmt{}
	_ DMLNode = &SplitRegionStmt{}

看到这里,是不是根常规sql操作对应上了?

解决思路

由于项目只是为了提供只读权限,只需要遍历解析出来的ast,只允许SelectStmt存在即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值