javacc实现简单SQL解析器
文章目录
- 前言
- 本章节源码
- 需求1:实现一个最简单的select sql
- 要求
- 实现jj文件
- 编译测试
- 需求2:理解Token及其他属性
- 说明
- javajj文件
- 需求3:实现解析得到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>{// 没有定义变量的场景,默认匹配到的单词为tokenSystem.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.jjtjavac SQLParser.javajava SQLParser "select id from changlu where a=1"
需求2:理解Token及其他属性
说明
JavaCC生成的SQL解析器中打印出Token的其他属性,你需要访问Token
对象的额外信息。默认情况下,JavaCC为每个匹配到的词法单元(token)创建一个Token
对象,这个对象不仅包含词法单元的文本内容(即image
),还包含了其他有用的信息,如行号(beginLine
和endLine
)、列号(beginColumn
和endColumn
)等。
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>{// 没有定义变量的场景,默认匹配到的单词为tokenToken t = token;System.out.println("Column: " + t.image+ ", Line: " + t.beginLine + ", end Line:" + t.endLine+ ", beginColumn: " + t.beginColumn + ", endColumn:" + t.endColumn);}
}// 表名规则
void tableName() :
{}
{<IDENTIFIER>{// 没有定义变量的场景,默认匹配到的单词为tokenToken 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语法树 & 精确点位
能力:解析出语法树,每个字段节点都能够解析到位点。
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);}@Overridepublic 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;}@Overridepublic void accept(ASTVisitor visitor) {visitor.visit(this);}@Overridepublic 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;}@Overridepublic void accept(ASTVisitor visitor) {visitor.visit(this);}@Overridepublic 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;}@Overridepublic void accept(ASTVisitor visitor) {visitor.visit(this);}@Overridepublic 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;}@Overridepublic void accept(ASTVisitor visitor) {visitor.visit(this);}@Overridepublic 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;}@Overridepublic 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--;}@Overridepublic void visit(Column column) {System.out.println(getIndent() + "COLUMN: " + column.name + formatPosition(column.position));}@Overridepublic void visit(Table table) {System.out.println(getIndent() + "FROM " + table.name + formatPosition(table.position));}@Overridepublic 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 tokentable = 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.jjtjavac *.javajava SQLParser "select id,name from users where id=1"
资料获取
大家点赞、收藏、关注、评论啦~
精彩专栏推荐订阅:在下方专栏👇🏻
- 长路-文章目录汇总(算法、后端Java、前端、运维技术导航):博主所有博客导航索引汇总
- 开源项目Studio-Vue—校园工作室管理系统(含前后台,SpringBoot+Vue):博主个人独立项目,包含详细部署上线视频,已开源
- 学习与生活-专栏:可以了解博主的学习历程
- 算法专栏:算法收录
更多博客与资料可查看👇🏻获取联系方式👇🏻,🍅文末获取开发资源及更多资源博客获取🍅