【この記事を読むのに必要な時間は約 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 は文字の大小を区別しませんので問題はないのですが、実装時には留意が必要となります。