javacc实现简单SQL解析器

 javacc实现简单SQL解析器

前言

博主介绍:✌目前全网粉丝4W+,csdn博客专家、Java领域优质创作者,博客之星、阿里云平台优质作者、专注于Java后端技术领域。

涵盖技术内容:Java后端、大数据、算法、分布式微服务、中间件、前端、运维等。

博主所有博客文件目录索引:博客目录索引(持续更新)

CSDN搜索:长路

视频平台:b站-Coder长路

本章节源码

当前文档配套相关源码地址:

  • gitee:https://gitee.com/changluJava/demo-exer/tree/master/java-sqlparser/demo-javacc/learn-javacc-demo
  • github:https://github.com/changluya/Java-Demos/tree/master/java-sqlparser/demo-javacc/learn-javacc-demo

需求1:实现一个最简单的select sql

要求

支持语法:
select * from table where a=1
select id from table where a=1
select id,name from table where a=2

实现jj文件

options {
    STATIC = false; // 动态生成解析器
}

PARSER_BEGIN(SQLParser)
import java.io.*;
public class SQLParser {
    public static void main(String[] args) throws ParseException, IOException {
        for (String arg : args) {
            Reader reader = new StringReader(arg);
            SQLParser parser = new SQLParser(reader);
            parser.sqlQuery();
            System.out.println("Parsing completed successfully.");
        }
    }
}
PARSER_END(SQLParser)

// 定义词法规则
SKIP : {
    " " | "\t" | "\n" | "\r"  // 跳过空白字符
}

TOKEN : {
    < SELECT: "select" >
    | < FROM: "from" >
    | < WHERE: "where" >
    | < IDENTIFIER: (["a"-"z", "A"-"Z"])+ >
    | < NUMBER: (["0"-"9"])+ >
    | < ASTERISK: "*" >  // 添加对 * 的支持
}

// 定义 SQL 查询的语法规则
void sqlQuery() :
{}
{
    <SELECT>
    ( columnListOrAsterisk() )
    <FROM> tableName()
    [ whereClause() ] <EOF>
    {
        System.out.println("Parsed SQL Query");
    }
}

// 列表规则或 *
void columnListOrAsterisk() :
{}
{
    <ASTERISK> { System.out.println("Column: *"); }
    |
    columnList()
}

// 列表规则
void columnList() :
{}
{
    columnName() ( "," columnName() )*
}

// 单个列名规则
void columnName() :
{}
{
    <IDENTIFIER>
    {
        // 没有定义变量的场景,默认匹配到的单词为token
        System.out.println("Column: " + token.image);
    }
}

// 表名规则
void tableName() :
{}
{
    <IDENTIFIER>
    {
        System.out.println("Table: " + token.image);
    }
}

// WHERE 子句规则
void whereClause() :
{}
{
    <WHERE> condition()
}

// 条件规则
void condition() :
{
   // 如果某个方法中涉及到多个变量获取打印情况,则可以使用如下变量定义
   Token identifierToken, numberToken;
}
{
    // 读取值方式为: 变量名=<token> 匹配相应字符
    identifierToken=<IDENTIFIER> "=" numberToken=<NUMBER>
    {
        System.out.println("Condition: " + identifierToken.image + " = " + numberToken.image);
    }
}

编译测试

javacc demo03.jjt
  
javac SQLParser.java
  
java SQLParser "select id from changlu where a=1"

需求2:理解Token及其他属性

说明

JavaCC生成的SQL解析器中打印出Token的其他属性,你需要访问Token对象的额外信息。默认情况下,JavaCC为每个匹配到的词法单元(token)创建一个Token对象,这个对象不仅包含词法单元的文本内容(即image),还包含了其他有用的信息,如行号(beginLineendLine)、列号(beginColumnendColumn)等。

javajj文件

补充打印了token的image、beginLine、endLine、beginColumn、endColumn

options {
    STATIC = false; // 动态生成解析器
}

PARSER_BEGIN(SQLParser)
import java.io.*;
public class SQLParser {
    public static void main(String[] args) throws ParseException, IOException {
        for (String arg : args) {
            Reader reader = new StringReader(arg);
            SQLParser parser = new SQLParser(reader);
            parser.sqlQuery();
            System.out.println("Parsing completed successfully.");
        }
    }
}
PARSER_END(SQLParser)

// 定义词法规则
SKIP : {
    " " | "\t" | "\n" | "\r"  // 跳过空白字符
}

TOKEN : {
    < SELECT: "select" >
    | < FROM: "from" >
    | < WHERE: "where" >
    | < IDENTIFIER: (["a"-"z", "A"-"Z"])+ >
    | < NUMBER: (["0"-"9"])+ >
    | < ASTERISK: "*" >  // 添加对 * 的支持
}

// 定义 SQL 查询的语法规则
void sqlQuery() :
{}
{
    <SELECT>
    ( columnListOrAsterisk() )
    <FROM> tableName()
    [ whereClause() ] <EOF>
    {
        System.out.println("Parsed SQL Query");
    }
}

// 列表规则或 *
void columnListOrAsterisk() :
{}
{
    <ASTERISK> { System.out.println("Column: *"); }
    |
    columnList()
}

// 列表规则
void columnList() :
{}
{
    columnName() ( "," columnName() )*
}

// 单个列名规则
void columnName() :
{}
{
    <IDENTIFIER>
    {
        // 没有定义变量的场景,默认匹配到的单词为token
        Token t = token;
        System.out.println("Column: " + t.image
                           + ", Line: " + t.beginLine + ", end Line:" + t.endLine
                           + ", beginColumn: " + t.beginColumn + ", endColumn:" + t.endColumn);
    }
}

// 表名规则
void tableName() :
{}
{
    <IDENTIFIER>
    {
        // 没有定义变量的场景,默认匹配到的单词为token
        Token t = token;
        System.out.println("Table: " + t.image
                           + ", Line: " + t.beginLine + ", end Line:" + t.endLine
                           + ", beginColumn: " + t.beginColumn + ", endColumn:" + t.endColumn);
    }
}

// WHERE 子句规则
void whereClause() :
{}
{
    <WHERE> condition()
}

// 条件规则
void condition() :
{
   // 如果某个方法中涉及到多个变量获取打印情况,则可以使用如下变量定义
   Token identifierToken, numberToken;
}
{
    // 读取值方式为: 变量名=<token> 匹配相应字符
    identifierToken=<IDENTIFIER> "=" numberToken=<NUMBER>
    {
        System.out.println("Condition: " + identifierToken.image + " = " + numberToken.image);
        System.out.println("identifierToken: " + identifierToken.image
                           + ", Line: " + identifierToken.beginLine + ", end Line:" + identifierToken.endLine
                           + ", beginColumn: " + identifierToken.beginColumn + ", endColumn:" + identifierToken.endColumn);
        System.out.println("numberToken: " + numberToken.image
                           + ", Line: " + numberToken.beginLine + ", end Line:" + numberToken.endLine
                           + ", beginColumn: " + numberToken.beginColumn + ", endColumn:" + numberToken.endColumn);
    }
}

需求3:实现解析得到SQL语法树 & 精确点位

img

能力:解析出语法树,每个字段节点都能够解析到位点。

SqlParser.jjt:

options {
    STATIC = false;
}

PARSER_BEGIN(SQLParser)
import java.io.*;
import java.util.ArrayList;
import java.util.List;

// 位置信息类
class TokenPosition {
    public final int startLine;
    public final int startColumn;
    public final int endLine;
    public final int endColumn;

    public TokenPosition(int startLine, int startColumn, int endLine, int endColumn) {
        this.startLine = startLine;
        this.startColumn = startColumn;
        this.endLine = endLine;
        this.endColumn = endColumn;
    }

    public TokenPosition(Token token) {
        this(token.beginLine, token.beginColumn, token.endLine, token.endColumn);
    }

    @Override
    public String toString() {
        return "[" + startLine + ":" + startColumn + "-" + endLine + ":" + endColumn + "]";
    }
}

// 语法树节点接口
interface ASTNode {
    void accept(ASTVisitor visitor);
    TokenPosition getPosition();
}

// SELECT语句节点
class SelectStatement implements ASTNode {
    public final TokenPosition position;
    public final List<Column> columns;
    public final Table table;
    public final Condition whereCondition;

    public SelectStatement(TokenPosition position, List<Column> columns, Table table, Condition whereCondition) {
        this.position = position;
        this.columns = columns;
        this.table = table;
        this.whereCondition = whereCondition;
    }

    @Override
    public void accept(ASTVisitor visitor) {
        visitor.visit(this);
    }

    @Override
    public TokenPosition getPosition() {
        return position;
    }
}

// 列节点
class Column implements ASTNode {
    public final TokenPosition position;
    public final String name;

    public Column(TokenPosition position, String name) {
        this.position = position;
        this.name = name;
    }

    @Override
    public void accept(ASTVisitor visitor) {
        visitor.visit(this);
    }

    @Override
    public TokenPosition getPosition() {
        return position;
    }
}

// 表节点
class Table implements ASTNode {
    public final TokenPosition position;
    public final String name;

    public Table(TokenPosition position, String name) {
        this.position = position;
        this.name = name;
    }

    @Override
    public void accept(ASTVisitor visitor) {
        visitor.visit(this);
    }

    @Override
    public TokenPosition getPosition() {
        return position;
    }
}

// WHERE条件节点
class Condition implements ASTNode {
    public final TokenPosition position;
    public final Column column;
    public final String value;
    public final TokenPosition valuePosition;

    public Condition(TokenPosition position, Column column, String value, TokenPosition valuePosition) {
        this.position = position;
        this.column = column;
        this.value = value;
        this.valuePosition = valuePosition;
    }

    @Override
    public void accept(ASTVisitor visitor) {
        visitor.visit(this);
    }

    @Override
    public TokenPosition getPosition() {
        return position;
    }
}

// 访问者接口
interface ASTVisitor {
    void visit(SelectStatement select);
    void visit(Column column);
    void visit(Table table);
    void visit(Condition condition);
}

// 示例访问者实现
class PrintVisitor implements ASTVisitor {
    private int indent = 0;

    private String getIndent() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < indent; i++) {
            sb.append("  ");
        }
        return sb.toString();
    }

    private String formatPosition(TokenPosition pos) {
        return " " + pos;
    }

    @Override
    public void visit(SelectStatement select) {
        System.out.println(getIndent() + "SELECT" + formatPosition(select.position));
        indent++;
        for (Column col : select.columns) {
            col.accept(this);
        }
        select.table.accept(this);
        if (select.whereCondition != null) {
            select.whereCondition.accept(this);
        }
        indent--;
    }

    @Override
    public void visit(Column column) {
        System.out.println(getIndent() + "COLUMN: " + column.name + formatPosition(column.position));
    }

    @Override
    public void visit(Table table) {
        System.out.println(getIndent() + "FROM " + table.name + formatPosition(table.position));
    }

    @Override
    public void visit(Condition condition) {
        System.out.println(getIndent() + "WHERE " +
            condition.column.name + " = " + condition.value +
            formatPosition(condition.position) +
            " (value at" + formatPosition(condition.valuePosition) + ")");
    }
}

public class SQLParser {
    private SelectStatement selectStatement;

    public SelectStatement getAST() {
        return selectStatement;
    }

    public static void main(String[] args) throws ParseException, IOException {
        String sql = args.length > 0 ? args[0] : "select id, name from users where id = 1";
        Reader reader = new StringReader(sql);
        SQLParser parser = new SQLParser(reader);

        try {
            parser.sqlQuery();
            System.out.println("Parsing completed successfully.");

            SelectStatement ast = parser.getAST();
            System.out.println("\nAbstract Syntax Tree with Positions:");
            ast.accept(new PrintVisitor());

        } catch (ParseException e) {
            System.err.println("SQL parse error: " + e.getMessage());
        }
    }
}
PARSER_END(SQLParser)

// 词法规则
SKIP : { " " | "\t" | "\n" | "\r" }
TOKEN : {
    < SELECT: "select" >
    | < FROM: "from" >
    | < WHERE: "where" >
    | < IDENTIFIER: (["a"-"z","A"-"Z"])+ >
    | < NUMBER: (["0"-"9"])+ >
    | < ASTERISK: "*" >
}

// 语法规则
void sqlQuery() :
{
    Token selectToken;
    List<Column> columns = new ArrayList<>();
    Table table;
    Condition whereCondition = null;
    TokenPosition selectPosition;
}
{
    selectToken = <SELECT>
    columns = columnListOrAsterisk(selectToken)
    <FROM>  // Explicitly consume FROM token
    table = tableName()
    [ whereCondition = whereClause() ] <EOF>
    {
        selectPosition = new TokenPosition(
            selectToken.beginLine, selectToken.beginColumn,
            token.endLine, token.endColumn
        );
        selectStatement = new SelectStatement(selectPosition, columns, table, whereCondition);
    }
}

List<Column> columnListOrAsterisk(Token selectToken) :
{
    List<Column> columns = new ArrayList<>();
    Token token;
}
{
    (
        token = <ASTERISK>
        {
            columns.add(new Column(new TokenPosition(token), "*"));
        }
        |
        columns = columnList()
    )
    { return columns; }
}

List<Column> columnList() :
{
    List<Column> columns = new ArrayList<>();
    Column column;
}
{
    column = columnName() { columns.add(column); }
    ( "," column = columnName() { columns.add(column); } )*
    { return columns; }
}

Column columnName() :
{
    Token t;
}
{
    t = <IDENTIFIER>
    {
        return new Column(new TokenPosition(t), t.image);
    }
}

Table tableName() :
{
    Token t;
}
{
    t = <IDENTIFIER>  // Just parse the identifier, FROM is already handled
    {
        return new Table(new TokenPosition(t), t.image);
    }
}

Condition whereClause() :
{
    Token whereToken;
    Condition condition;
}
{
    whereToken = <WHERE> condition = condition()
    {
        return condition;
    }
}

Condition condition() :
{
    Column column;
    Token operator, valueToken;
    String value;
}
{
    column = columnName()
    operator = "="
    (
        valueToken = <NUMBER>
        {
            value = valueToken.image;
        }
        |
        valueToken = <IDENTIFIER>
        {
            value = valueToken.image;
        }
    )
    {
        return new Condition(
            new TokenPosition(
                column.getPosition().startLine,
                column.getPosition().startColumn,
                valueToken.endLine,
                valueToken.endColumn
            ),
            column,
            value,
            new TokenPosition(valueToken)
        );
    }
}

执行命令解析:

javacc SqlParser.jjt

javac *.java

java SQLParser "select id,name from users where id=1"

资料获取

大家点赞、收藏、关注、评论啦~

精彩专栏推荐订阅:在下方专栏👇🏻

更多博客与资料可查看👇🏻获取联系方式👇🏻,🍅文末获取开发资源及更多资源博客获取🍅

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长路 ㅤ   

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值