基於Java的SQL解析工具的比較與學習
阿新 • • 發佈:2019-01-27
1、JSqlParser
gtihub 地址: https://github.com/JSQLParser/JSqlParser.git
使用方法:
/** * * @Package: com.yonyou.splice * @author: caozq * @date: 2018年6月26日 下午12:03:52 */ package com.yonyou.splice; import java.io.StringReader; import java.util.List; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.BinaryExpression; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.parser.CCJSqlParserManager; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.insert.Insert; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.update.Update; /** * @ClassName: JsqlparserDemo * @Description: TODO * @author: caozq * @date: 2018年6月26日 下午12:03:52 */ public class JsqlparserDemo { public static void main(String[] args) throws JSQLParserException { insertIntoDemo(); } public static void insertIntoDemo() throws JSQLParserException { CCJSqlParserManager pm = new CCJSqlParserManager(); StringBuffer stringBuffer = new StringBuffer(); //insert into metrics_03 select pk,money,0 from metrics_01 //stringBuffer.append("insert into tbl_name1(col1,col2) select col3,col4 from tbl_name2"); // stringBuffer.append(" INSERT INTO FAE_CFG_CRI_AMOUNTTYPE (") // .append(" PK_AMOUNTTYPECRITERION,") // .append(" PK_AMOUNTTYPE,") // .append(" AMOUNTTYPE_CODE") // .append(" )") // .append(" SELECT") // .append(" B.PK_AMOUNTTYPECRITERION,") // .append(" C.PK_AMOUNTTYPE,C.CODE AMOUNTTYPE_CODE") // .append(" FROM FAE_AMOUNTTYPECRITERION_B A INNER JOIN FAE_AMOUNTTYPECRITERION B") // .append(" ON A.PK_AMOUNTTYPECRITERION=B.PK_AMOUNTTYPECRITERION") // .append(" LEFT JOIN FAE_AMOUNTTYPE C") // .append(" ON A.PK_AMOUNTTYPE=C.PK_AMOUNTTYPE") // .append(" WHERE NVL(A.DR,0)=0") // .append(" AND NVL(B.ISENABLE,'Y')='Y' AND NVL(B.DR,0)=0") // .append(" AND NVL(C.ISENABLE,'Y')='Y' AND NVL(C.DR,0)=0"); stringBuffer.append(" INSERT INTO FAE_VOUCHER_B") .append(" PK_AMOUNTTYPECRITERION,") .append(" PK_AMOUNTTYPE,") .append(" AMOUNTTYPE_CODE") .append(" )") .append(" WITH FAE_ASSFLOW_B_TEMP AS (") .append(" SELECT") .append(" T1.RECORD_DT ,") .append(" T1.DEAL_DT ,") .append(" T1.DEAL_CODE ") .append(" CASE WHEN T1.CDFLAG='C' THEN DEAL_AMT") .append(" WHEN T1.CDFLAG='D' THEN -DEAL_AMT") .append(" ELSE 0 END DEAL_AMT") .append(" ON A.PK_AMOUNTTYPE=C.PK_AMOUNTTYPE") .append(" WHERE NVL(A.DR,0)=0") .append(" AND NVL(B.ISENABLE,'Y')='Y' AND NVL(B.DR,0)=0") .append(" AND NVL(C.ISENABLE,'Y')='Y' AND NVL(C.DR,0)=0"); Statement statement = pm.parse(new StringReader(stringBuffer.toString())); if (statement instanceof Insert) { // 獲得Update物件 Insert istatement = (Insert) statement; // 獲得表名 System.out.println("table:" + istatement.getTable()); List<Column> columns = istatement.getColumns(); if(null == columns){ columns = null;//獲取所有的表字段 } // Select sele = istatement.getSelect(); PlainSelect body = (PlainSelect) sele.getSelectBody(); String selectStr = body.toString(); System.out.println("-------"); // 獲得where條件表示式 Expression where = null; if(istatement.getSetExpressionList().size()!=0){ where = istatement.getSetExpressionList().get(0); // 初始化接收穫得到的欄位資訊 StringBuffer allColumnNames = new StringBuffer(); // BinaryExpression包括了整個where條件, // 例如:AndExpression/LikeExpression/OldOracleJoinBinaryExpression if (where instanceof BinaryExpression) { allColumnNames = getColumnName((BinaryExpression) (where), allColumnNames); System.out.println("allColumnNames:" + allColumnNames.toString()); } } } } public static void updateDemo() throws JSQLParserException { CCJSqlParserManager pm = new CCJSqlParserManager(); StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("update ac_operator op "); stringBuffer.append("set op.errcount=("); stringBuffer.append("(select case when op1.errcount is null then 0 else op1.errcount end as errcount "); stringBuffer.append("from ac_operator op1 "); stringBuffer.append("where op1.loginname = '中國' )+1"); stringBuffer.append("),lastlogin='中國' "); stringBuffer.append("where PROCESS_ID="); stringBuffer.append("(select distinct g.id from tempTable g where g.ID='中國')"); stringBuffer.append("and columnName2 = '890' and columnName3 = '678' and columnName4 = '456'"); Statement statement = pm.parse(new StringReader(stringBuffer.toString())); if (statement instanceof Update) { // 獲得Update物件 Update updateStatement = (Update) statement; // 獲得表名 System.out.println("table:" + updateStatement.getTables().get(0).getName()); List<Column> columns = updateStatement.getColumns(); for (Column column : columns) { System.out.println(column.getColumnName()); } // 獲得where條件表示式 Expression where = updateStatement.getWhere(); // 初始化接收穫得到的欄位資訊 StringBuffer allColumnNames = new StringBuffer(); // BinaryExpression包括了整個where條件, // 例如:AndExpression/LikeExpression/OldOracleJoinBinaryExpression if (where instanceof BinaryExpression) { allColumnNames = getColumnName((BinaryExpression) (where), allColumnNames); System.out.println("allColumnNames:" + allColumnNames.toString()); } } } /** * 獲得where條件欄位中列名,以及對應的操作符 @Title: getColumnName @Description: * TODO(這裡用一句話描述這個方法的作用) @param @param expression @param @param * allColumnNames @param @return 設定檔案 @return StringBuffer 返回型別 @throws */ private static StringBuffer getColumnName(Expression expression, StringBuffer allColumnNames) { String columnName = null; if (expression instanceof BinaryExpression) { // 獲得左邊表示式 Expression leftExpression = ((BinaryExpression) expression).getLeftExpression(); // 如果左邊表示式為Column物件,則直接獲得列名 if (leftExpression instanceof Column) { // 獲得列名 columnName = ((Column) leftExpression).getColumnName(); allColumnNames.append(columnName); allColumnNames.append(":"); // 拼接操作符 allColumnNames.append(((BinaryExpression) expression).getStringExpression()); // allColumnNames.append("-"); } // 否則,進行迭代 else if (leftExpression instanceof BinaryExpression) { getColumnName((BinaryExpression) leftExpression, allColumnNames); } // 獲得右邊表示式,並分解 Expression rightExpression = ((BinaryExpression) expression).getRightExpression(); if (rightExpression instanceof BinaryExpression) { Expression leftExpression2 = ((BinaryExpression) rightExpression).getLeftExpression(); if (leftExpression2 instanceof Column) { // 獲得列名 columnName = ((Column) leftExpression2).getColumnName(); allColumnNames.append("-"); allColumnNames.append(columnName); allColumnNames.append(":"); // 獲得操作符 allColumnNames.append(((BinaryExpression) rightExpression).getStringExpression()); } } } return allColumnNames; } }
2 Apache Calcite(以Mysql為例)
public static String convertMySql(String sql) throws SQLException, SqlParseException, ValidationException, RelConversionException { Connection aConnection = JDBCConnection.getInstance().getConnection(); try{ SqlDialectFactory aSqlDialectFactory = new SqlDialectFactoryImpl(); SqlDialect aSqlDialect = aSqlDialectFactory.create(aConnection.getMetaData()); SqlDialect.DatabaseProduct aDatabaseProduct = SqlDialect.getProduct(aConnection.getMetaData().getDatabaseProductName(), "UNUSED"); String schemaName = getSchemaName(aDatabaseProduct, aConnection); String catalog = aConnection.getCatalog(); FrameworkConfig aConfig = createConfig(dataSource, aDatabaseProduct, catalog, schemaName); Planner aPlanner = Frameworks.getPlanner(aConfig); SqlNode aQuery = aPlanner.parse(sql); aQuery = aPlanner.validate(aQuery); RelNode aRelNode = aPlanner.rel(aQuery).project(); RelToSqlConverter aSqlConverter = new RelToSqlConverter(aSqlDialect); SqlNode aSqlNode = aSqlConverter.visitChild(0, aRelNode).asStatement(); System.out.println(sql); System.out.println(aQuery); System.out.println(RelOptUtil.toString(aRelNode)); System.out.println(aSqlNode); return sql; }catch(Exception e){ e.getStackTrace(); } return null; }
3 JavaCC
JavaCC 是一個詞法分析生成器和語法分析生成器。 詞法分析和語法分析是處理輸入字元序列的軟體構件, 編譯器和直譯器協同詞法分析和語法分析來“解密” 程式檔案。
使用遞迴下降語法解析,LL(k)。其中,第一個L表示從左到右掃描輸入;第二個L表示每次都進行最左推導(在推導語法樹的過程中每次都替換句型中最左的非終結符為終結符。類似還有最右推導);k表示的是每次向前探索(lookahead)k個終結符
詞法規則,語法規則定義在同一檔案中,就是.jj檔案。
jjTree可以幫助更好的語法分析(因為好像沒用過,不好說啊)
可定製生成的行為,如對字母的大小寫是否敏感。不如設計資料庫sql語句的時候應該使用關鍵字大小寫不敏感。
更向前一步解決移進規約。當文法本身存在二義性的時候有時候通過設定lookahead為k能解決問題,帶來的問題就是增加編譯時間,所以最好的方法是修改二義性文法為無二義性文法。
參考:https://blog.csdn.net/newpidian/article/details/52964017