文章目錄
- 前言
- 本章節源碼
- 需求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):博主個人獨立項目,包含詳細部署上線視頻,已開源
- 學習與生活-專欄:可以了解博主的學習歷程
- 算法專欄:算法收錄
更多博客與資料可查看👇🏻獲取聯系方式👇🏻,🍅文末獲取開發資源及更多資源博客獲取🍅