文章目录
前言
博主介绍:✌目前全网粉丝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
),还包含了其他有用的信息,如行号(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>
{
// 没有定义变量的场景,默认匹配到的单词为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语法树 & 精确点位
能力:解析出语法树,每个字段节点都能够解析到位点。
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"
资料获取
大家点赞、收藏、关注、评论啦~
精彩专栏推荐订阅:在下方专栏👇🏻
- 长路-文章目录汇总(算法、后端Java、前端、运维技术导航):博主所有博客导航索引汇总
- 开源项目Studio-Vue—校园工作室管理系统(含前后台,SpringBoot+Vue):博主个人独立项目,包含详细部署上线视频,已开源
- 学习与生活-专栏:可以了解博主的学习历程
- 算法专栏:算法收录
更多博客与资料可查看👇🏻获取联系方式👇🏻,🍅文末获取开发资源及更多资源博客获取🍅