Skip to content

Latest commit

 

History

History
261 lines (194 loc) · 8.08 KB

File metadata and controls

261 lines (194 loc) · 8.08 KB

SQL 解析器指南 | SQL Parser Guide

English | 中文


中文

Druid SQL 解析器是一个高性能、支持多方言的 SQL 解析引擎,可将 SQL 文本解析为 AST(抽象语法树),支持 SQL 格式化、Schema 分析、SQL 改写等多种场景。

快速入门

解析 SQL

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.DbType;

String sql = "SELECT u.id, u.name, o.amount "
           + "FROM users u JOIN orders o ON u.id = o.user_id "
           + "WHERE o.amount > 100 ORDER BY o.amount DESC";

// 解析为 AST
List<SQLStatement> stmts = SQLUtils.parseStatements(sql, DbType.mysql);
SQLStatement stmt = stmts.get(0);

格式化 SQL

String ugly = "select id,name from users where age>18 and status='active' order by name";

// 格式化输出
String formatted = SQLUtils.format(ugly, DbType.mysql);
// 输出:
// SELECT id, name
// FROM users
// WHERE age > 18
//   AND status = 'active'
// ORDER BY name

// 带配置的格式化
SQLUtils.FormatOption option = new SQLUtils.FormatOption();
option.setUppCase(true);     // 关键字大写(注意:API 方法名 setUppCase 双 p 是源码中的已有拼写)
option.setPrettyFormat(true); // 美化格式
String result = SQLUtils.format(ugly, DbType.mysql, option);

Schema 分析

import com.alibaba.druid.sql.visitor.SchemaStatVisitor;

String sql = "SELECT u.name, o.total FROM users u "
           + "JOIN orders o ON u.id = o.user_id "
           + "WHERE u.age > 18";

List<SQLStatement> stmts = SQLUtils.parseStatements(sql, DbType.mysql);
SchemaStatVisitor visitor = SQLUtils.createSchemaStatVisitor(DbType.mysql);
stmts.get(0).accept(visitor);

// 获取表信息
System.out.println("Tables: " + visitor.getTables());
// 输出: {users=Select, orders=Select}

// 获取列信息
System.out.println("Columns: " + visitor.getColumns());
// 输出: [users.name, orders.total, users.id, orders.user_id, users.age]

// 获取 WHERE 条件
System.out.println("Conditions: " + visitor.getConditions());
// 输出: [users.id = orders.user_id, users.age > 18]

核心 API

SQLUtils — 入口工具类

方法 说明
parseStatements(sql, dbType) 解析 SQL 文本为语句列表
parseSingleStatement(sql, dbType, features...) 解析单条 SQL 语句(可传入 SQLParserFeature 可变参数)
format(sql, dbType) 格式化 SQL
format(sql, dbType, option) 带选项格式化 SQL
toSQLString(sqlObject, dbType) AST 节点输出为 SQL 文本
createSchemaStatVisitor(dbType) 创建 Schema 统计访问者

AST 核心节点

节点类型 基类 说明
语句 SQLStatement 一条完整的 SQL 语句(SELECT、INSERT、CREATE TABLE 等)
表达式 SQLExpr 表达式节点(列引用、常量、函数调用、运算等)
表源 SQLTableSource FROM 子句中的表引用
数据类型 SQLDataType 列的数据类型定义

常见 SQLStatement 子类:

对应 SQL
SQLSelectStatement SELECT
SQLInsertStatement INSERT
SQLUpdateStatement UPDATE
SQLDeleteStatement DELETE
SQLCreateTableStatement CREATE TABLE
SQLAlterTableStatement ALTER TABLE
SQLDropTableStatement DROP TABLE
SQLCreateIndexStatement CREATE INDEX

常见 SQLExpr 子类:

说明 示例
SQLIdentifierExpr 标识符 nameusers
SQLPropertyExpr 属性表达式 u.namedb.table
SQLIntegerExpr 整数常量 42
SQLCharExpr 字符串常量 'hello'
SQLBinaryOpExpr 二元运算 a > 10x + y
SQLMethodInvokeExpr 函数调用 COUNT(*)NOW()
SQLInListExpr IN 列表 id IN (1, 2, 3)
SQLBetweenExpr BETWEEN age BETWEEN 18 AND 30

Visitor 模式

Druid 使用 Visitor 模式遍历 AST。核心访问者接口:

Visitor 功能
SQLASTVisitor 基础访问者接口
SQLASTOutputVisitor SQL 输出(AST → SQL 文本)
SchemaStatVisitor Schema 统计(提取表、列、条件信息)
Dialect-specific Visitors 各方言特化的访问者

自定义 Visitor 示例

import com.alibaba.druid.sql.visitor.SQLASTVisitorAdapter;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;

// 统计所有二元运算表达式
public class BinaryOpCounter extends SQLASTVisitorAdapter {
    private int count = 0;

    @Override
    public boolean visit(SQLBinaryOpExpr x) {
        count++;
        return true;  // 继续遍历子节点
    }

    public int getCount() { return count; }
}

// 使用
BinaryOpCounter counter = new BinaryOpCounter();
stmt.accept(counter);
System.out.println("Binary ops: " + counter.getCount());

SQL 改写

通过操作 AST 实现 SQL 改写:

// 添加 WHERE 条件
String sql = "SELECT * FROM users";
SQLSelectStatement stmt = (SQLSelectStatement) SQLUtils.parseSingleStatement(sql, DbType.mysql);
SQLSelectQueryBlock queryBlock = stmt.getSelect().getQueryBlock();

// 添加 tenant_id 条件
SQLBinaryOpExpr condition = new SQLBinaryOpExpr(
    new SQLIdentifierExpr("tenant_id"),
    SQLBinaryOperator.Equality,
    new SQLIntegerExpr(123)
);
queryBlock.addWhere(condition);

System.out.println(SQLUtils.toSQLString(stmt, DbType.mysql));
// 输出: SELECT * FROM users WHERE tenant_id = 123

方言特定解析

不同方言有特定的语法扩展。使用正确的 DbType 以获得最佳解析结果:

// MySQL 特有语法
SQLUtils.parseStatements("SELECT * FROM t LIMIT 10, 20", DbType.mysql);

// PostgreSQL 特有语法
SQLUtils.parseStatements("SELECT * FROM t LIMIT 20 OFFSET 10", DbType.postgresql);

// Oracle 特有语法
SQLUtils.parseStatements("SELECT * FROM t WHERE ROWNUM <= 20", DbType.oracle);

性能建议

  1. 重用 Parser — 在高频场景中避免重复创建 Parser 对象
  2. 选择正确的 DbType — 使用精确的方言类型,避免使用通用解析模式
  3. 按需遍历 — 使用 return false 跳过不需要遍历的子树

English

The Druid SQL Parser is a high-performance, multi-dialect SQL parsing engine that converts SQL text into AST (Abstract Syntax Tree), supporting SQL formatting, schema analysis, SQL rewriting and more.

Quick Start

// Parse SQL
List<SQLStatement> stmts = SQLUtils.parseStatements(sql, DbType.mysql);

// Format SQL
String formatted = SQLUtils.format(sql, DbType.mysql);

// Schema analysis
SchemaStatVisitor visitor = SQLUtils.createSchemaStatVisitor(DbType.mysql);
stmts.get(0).accept(visitor);
System.out.println("Tables: " + visitor.getTables());
System.out.println("Columns: " + visitor.getColumns());

Core API

  • SQLUtils.parseStatements(sql, dbType) — Parse SQL text into statement list
  • SQLUtils.format(sql, dbType) — Format SQL
  • SQLUtils.toSQLString(ast, dbType) — Convert AST back to SQL text
  • SQLUtils.createSchemaStatVisitor(dbType) — Create schema analysis visitor

AST Node Hierarchy

  • SQLStatement — Complete SQL statements (SELECT, INSERT, CREATE TABLE, etc.)
  • SQLExpr — Expressions (column refs, constants, function calls, operations)
  • SQLTableSource — Table references in FROM clauses
  • SQLDataType — Column data type definitions

Visitor Pattern

Use the Visitor pattern to traverse and transform the AST:

  • SQLASTVisitor — Base visitor interface
  • SQLASTOutputVisitor — SQL output generation
  • SchemaStatVisitor — Schema statistics extraction
  • Custom visitors — Extend SQLASTVisitorAdapter for custom logic

SQL Rewriting

Manipulate the AST to rewrite SQL:

SQLSelectStatement stmt = (SQLSelectStatement) SQLUtils.parseSingleStatement(sql, DbType.mysql);
SQLSelectQueryBlock queryBlock = stmt.getSelect().getQueryBlock();
queryBlock.addWhere(new SQLBinaryOpExpr(
    new SQLIdentifierExpr("tenant_id"),
    SQLBinaryOperator.Equality,
    new SQLIntegerExpr(123)
));
String rewritten = SQLUtils.toSQLString(stmt, DbType.mysql);