【この記事を読むのに必要な時間は約 13 分です】
FoundationDB SQL Parser は、SQL 内で参照しているテーブル・カラムや Where / GOUP BY / ORDER BY 句等の条件、関数を抽出することができるフリー(Apache License 2.0)の Java のパーサです。
以下に実装例を示します。
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.foundationdb.sql.StandardException; import com.foundationdb.sql.parser.*; import com.foundationdb.sql.unparser.NodeToString; public class SqlParserTest { /** Logger. */ private static Log log = LogFactory.getLog(SqlParserTest.class); public String getSchemaName() { return schemaName; } public static void main (String arg[]) throws StandardException { String sql = "SELECT PRODUCT.NAME, PRODUCT.PRICE " + "FROM TB_SALES_INFO SALES, TB_MST_PRODUCT PRODUCT " + "WHERE PRODUCT.DEAL_CD NOT IN ('001','007') " + "AND SUBSTR(SALES.RGST_DATE, 1, 8) = '20141101' " + "AND PRODUCT.DELETE_FLG <> '1' " + "GROUP BY SALES.SECTION_CD"; SQLParser parser = new SQLParser(); QueryVisitor qVis = new QueryVisitor(); StatementNode stmt = null; try { // パース実行 stmt = parser.parseStatement(sql); stmt.accept(qVis); // debug //stmt.treePrint(); } catch (StandardException e) { log.error("SQとして不正である場合はここでエラー"); } // 1.ステートメント System.out.println("statement : " + stmt.statementToString()); // 2.テーブル名 for (FromTable table : qVis.fromList) { String tableName = table.getTableName().toString(); System.out.println("table name : " + tableName); } // 3.selectフィールド名 NodeToString nodeToString = new NodeToString(); System.out.println("field name : " + nodeToString.toString(qVis.resultList)); /* * statement : SELECT table name : sales table name : product field name : product.name AS name, product.price AS price */ // NodeType によって Where 条件を解析 // 複数の Where 条件がある場合、ネストされている switch (qVis.whereClauses.getNodeType()) { case NodeTypes.AND_NODE: AndNode andNode = (AndNode) qVis.whereClauses; // 左辺 ValueNode leftNode = andNode.getLeftOperand(); // 右辺 ValueNode rightNode = andNode.getRightOperand(); break; case NodeTypes.OR_NODE: OrNode orNode = (OrNode) qVis.whereClauses; // 左辺 ValueNode orLeftNode = orNode.getLeftOperand(); // 右辺 ValueNode orRightNode = orNode.getRightOperand(); break; case NodeTypes.IN_LIST_OPERATOR_NODE: InListOperatorNode inOpeNode = (InListOperatorNode)qVis.whereClauses; break; // BinaryRelationalOperatorNode // This class represents the 6 binary operators: // LessThan, LessThanEquals, Equals, NotEquals, // GreaterThan and GreaterThanEquals. case NodeTypes.BINARY_EQUALS_OPERATOR_NODE: case NodeTypes.BINARY_GREATER_EQUALS_OPERATOR_NODE: case NodeTypes.BINARY_GREATER_THAN_OPERATOR_NODE: case NodeTypes.BINARY_LESS_EQUALS_OPERATOR_NODE: case NodeTypes.BINARY_LESS_THAN_OPERATOR_NODE: case NodeTypes.BINARY_NOT_EQUALS_OPERATOR_NODE: // 比較演算 break; default: break; } } }
32行目のパースによりにパース結果がノード構造で StatementNode に保持されます。さらに33 行目 accept() メソッドの実行により、子ノードを以下の独自 Visitor クラス内で WHERE、FROM、GROUP BY、ORDER BY、などに区別します。
import com.foundationdb.sql.StandardException; import com.foundationdb.sql.parser.*; public class QueryVisitor implements Visitor { public ResultColumnList resultList; public FromList fromList; public ValueNode whereClauses; public ValueNode havingClauses; public GroupByList groupList; public OrderByList orderbyList; @Override public Visitable visit(Visitable visitable) { QueryTreeNode node = (QueryTreeNode) visitable; switch (node.getNodeType()) { case NodeTypes.SELECT_NODE: SelectNode sn = (SelectNode) node; resultList = sn.getResultColumns(); fromList = sn.getFromList(); whereClauses = sn.getWhereClause(); havingClauses = sn.getHavingClause(); groupList = sn.getGroupByList(); break; case NodeTypes.CURSOR_NODE: orderbyList = ((CursorNode)node).getOrderByList(); break; default: break; } return visitable; } @Override public boolean visitChildrenFirst(Visitable node) { return false; } @Override public boolean stopTraversal() { return false; } @Override public boolean skipChildren(Visitable node) throws StandardException { return false; } }
40~53行目では、ステートメントと参照するテーブル名、カラム名を抽出しています。結果は56~59行目のようになります。
64行目以降では、WHERE 句の内容を判定しています。ValueNode が条件によってネストされていく構造ですので、実際には条件の数だけループする必要があります。
whereClauses は、実行時には以下のような構造で条件を保持しています(抜粋)。 左辺(leftOperand)と右辺(rightOperand)を演算子(operator)によってネストの深い位置から順に、繰り返し判定していきます。
演算子・被演算子(operand)にはそれぞれ NodeType が定められており、NodeType を判定することで条件を個別に解析していくことができます。
使用上の注意
FoundationDB SQL Parser の利用において注意すべき点としては、標準SQL (SQL-92 以降) に準拠している構文のみがサポート対象となることです。
基本的に Oracle, MySQL などのクエリは正常に解析することができますが、標準 SQL に含まれない一部の機能(たとえば LEAD )は構文エラーが発生してしまうため、その場合は以下の
また、パース時に SQL 内の大小文字の区別は保持されません。カラム名、テーブル名はパース後は小文字に変換されています。SQL は文字の大小を区別しませんので問題はないのですが、実装時には留意が必要となります。