Top / Java SQL Parserを調査する
SQLを解析することで、SQLを動的に解析して単体試験仕様書の作成を自動化したい
そのためには次の項目を自動抽出したい。
この記事を書くにあたっての目標である。
Oracleのプレースホルダー
さらにいうと、
SQLを解析し、Excelで定義してある和名を検索し和名表示に置換する。
そのためには、SQL解析にて、次の対応表を作成する。
WHERE句を解析するには正規表現などを駆使して文法を解析するのもいいが、できれば文法の定義ファイルをもとにパーサを生成し、そのパーサをもとに解析したい。
簡単な文法レベルでいいから手軽にパーサを生成して活用できるようになりたいと思う。
http://www.antlr.org/grammar/list
まずは上記のFAQから読み始めたほうがいいが、英語なので、英語が苦手ならば、日本語の説明サイトがあったので、そちらをみたほうがいい。
http://www.limy.org/program/java/antlr/step1.html
http://snakemanshow.blogspot.com/2008/02/antlr.html
よくわからない状態で、ためしにEclipseのビルドパスに外部Jarとしてぶっこんで、
http://www.antlr.org/wiki/display/ANTLR3/ANTLR+Cheat+Sheet
をためしてみたが、TLexerとTParserが見つからない、どうやら事前に生成するようだ。
expr.gで下記の内容を保存してみる。
class ExprParser extends Parser; expr: mexpr ((PLUS|MINUS) mexpr)* ; mexpr: atom (STAR atom)* ; atom: INT | LPAREN expr RPAREN ;
class ExprLexer extends Lexer; options { k=2; // needed for newline junk charVocabulary='\u0000'..'\u007F'; // allow ascii } LPAREN: '(' ; RPAREN: ')' ; PLUS : '+' ; MINUS : '-' ; STAR : '*' ; INT : ('0'..'9')+ ; WS : ( ' ' | '\r' '\n' | '\n' | '\t' ) {$setType(Token.SKIP);} ;
次のようにコマンドラインで実行する
java -classpath antlr-3.2.jar antlr.Tool expr.g
なにやらファイルが生成された
http://www.antlr.org/grammar/1174072667394/PLSQLGrammar.g
上記ファイルをダウンロードして下記のように打ち込むと
java -classpath antlr-3.2.jar antlr.Tool PLSQLGrammar.g
クラスファイルが出来上がる。
で、下記のクラスを要求している
そのファイルは下記よりダウンロード可である。
http://www.antlr.org/grammar/1174072667394/SoftwareMetrics.java
でこのSoftwareMetrics?はいろいろ摩訶不思議な設定が必要だが、その方法は下記よりダウンロード可能
http://www.antlr.org/grammar/1174072667394/PLSQLMain.java
http://antlreclipse.sourceforge.net/
http://openjdk.java.net/projects/compiler-grammar/antlrworks/
http://openjdk.java.net/projects/compiler-grammar/antlrworks/
Apache Derby
はApacheのDBのサブプロジェクトでしてそのApache Derbyにパーサがあるらしい
.native() メソッドを見るといいようだ。
http://jsqlparser.sourceforge.net/
SQLを解析してJavaクラスの階層構造に変換する
Visitorパターンについて知りたい場合は下記のURLなどを見る
Visitorパターンって、Visitorが訪れるイベントしかとれないようなのは、 ありがたくない気がする。 なぜなら、構文は開始を終了が明確になっていないと、構文木を生成できないからだ。 だったら、はじめからjparsecをつかってたほうがいいってことになる。
http://www.aerith.net/design/Visitor-j.html
ダウンロードしてきたjarのパスをzipに変換して解凍し、 そのなかからlibフォルダにjarがあるので、そいつをeclipseのビルドパスに外部jarとして取り込ませる。
サイトのサンプルがちょっと手直しが必要だったので直して、日本語訳しておきます。
TablesNamesFinder?のStringValue?はEclipseの自動補完を使うとjava.langの方をつかうので、
import net.sf.jsqlparser.expression.StringValue;
としておきましょう
JoinVisitor?(だっけか?)は削除しておきます。
CCJSqlParserManager pm = new CCJSqlParserManager(); /* * Oracleのプレースホルダーは対応してないので、''で括るなどしましょう * :AAA -> ':AAA' * Oracleの外部結合である(+)も対応していないので、削っておきましょう * (+) -> 削除 */ String sql = "SELECT * FROM MY_TABLE1, MY_TABLE2, (SELECT * FROM MY_TABLE3) LEFT OUTER JOIN MY_TABLE4 "+ " WHERE ID = (SELECT MAX(ID) FROM MY_TABLE5) AND ID2 IN (SELECT * FROM MY_TABLE6)" ; net.sf.jsqlparser.statement.Statement statement = pm.parse(new StringReader(sql)); /* 対象のSQL文字列が何を行うか(たとえばSELECTなのかINSERTなのか...)に応じて、 StatementVisitorをimplementsで実装したクラスをつかってください。 とりあえずここでは例としてSELECT用のselectStatementをつかっています。 */ if (statement instanceof Select) { Select selectStatement = (Select) statement; TablesNamesFinder tablesNamesFinder = new TablesNamesFinder(); List tableList = tablesNamesFinder.getTableList(selectStatement); for (Iterator iter = tableList.iterator(); iter.hasNext();) { System.out.println(iter.next()); } }
サンプルにJavaのコードのパース例とか載っているし、 BNFファイルのパース例とかが載っていて、興味深い。 この手のパースプログラムは、その他のパースプログラムの定義もどん欲に取り込もうとすると おもわれる。なぜなら移植したほうが、いちから作成するよりも簡単だからだ。
構文木を生成して、別の言語。特に自分自身の言語でのパース定義ソースを生成することが、 便利なのだろう。
http://www.gibello.com/code/zql/
Javaで書かれたSQLのParser
OracleのDECODEとかの関数がデフォルトで定義されていない
p = new ZqlParser(); p.addCustomFunction("DECODE", 0);
とするが、引数の数値のエラーがでたまま解析が実行できない。
下記のアドレスが参考になりそうだが、MDLがよくわからんので役にはたたない。
http://blogs.oracle.com/warehousebuilder/2007/08/14/
cmd
でDOSプロンプト起動
cd xxxデモのあるディレクトリ
でデモファイルがあるディレクトリをカレントディレクトリにする。
READMEには
java ZDemo queries.num
とあるが、パスを通しておく
java -cp .;..\classes ZDemo queries.num
select * from num [a = 1.0, b = 1.0, c = 1.0, d = 1.0, e = 1.0] [a = 2.0, b = 2.0, c = 2.0, d = 2.0, e = 2.0] [a = 1.0, b = 2.0, c = 3.0, d = 4.0, e = 5.0] [a = 5.0, b = 4.0, c = 3.0, d = 2.0, e = 1.0] select * from num where ((1 + 1) = 2) [a = 1.0, b = 1.0, c = 1.0, d = 1.0, e = 1.0] [a = 2.0, b = 2.0, c = 2.0, d = 2.0, e = 2.0] [a = 1.0, b = 2.0, c = 3.0, d = 4.0, e = 5.0] [a = 5.0, b = 4.0, c = 3.0, d = 2.0, e = 1.0] select ((((a + b) + c) + d) + e) from num 5.0 10.0 15.0 15.0
略
java -cp .;..\classes StringDemo "select * from num where foo = bar order by fuga;"
結果
select * from num where (foo = bar) order by fuga ASC
http://byaccj.sourceforge.net/
YACCと互換性あるそうな。
すでに、YACCでの.yのファイルがたくさんある場合。たしかに、新しく定義書を一から作り上げるよりは、すでにあるものを活用した方が手っ取り早いかもしれない。
となると、参考になる構文解析の定義ファイル一覧を持っていないといけない。 ただし、その場合、自分で微調整できるスキルがないとだめだろう。
http://www2.cs.tum.edu/projects/cup/
bisonのようなボトムアップ型の構文解析のJavaコードを生成します。
JavaのParser生成ライブラリー
bisonのアルゴリズムの解説
http://www.bookshelf.jp/texi/bison/bison-ja_8.html
ちなみにC言語の構文解析を生成するのはbisonと呼ばれる
トップダウン型のJava構文解析の生成にはJavaCCがある。
JFlexはこちら
ちなみにC言語の字句解析器生成言語はflexという。
Python言語でいうところのSparkモジュールに相当するかも
Pythonのその他の構文解析モジュールについては下記URLがまとまっていた。興味が湧いたら見てみる。
http://nedbatchelder.com/text/python-parsers.html
パーサコンビネータを作っちゃう人の記事
http://inforno.net/articles/tag/javascript/
パーサ生成フレームワーク
YACCとの違いは外部ファイルを必要としない点が違う。
Ruby版も存在しており、rparsecという。言語の先頭1文字をとって区別をつけている。 haskell版もあるがこちらが、元になっているので、こちらの名前はparsecという。
jparsecをダウンロードし、解凍すると [jparsec-2.0_src]-[examples]-[src]-[org]-[codehaus]-[jparsec]-[examples]-[sql] がある。
jparsecからダウンロードしてきたファイルを解凍しておきます。
junitのjarファイルも手元になければ、ダウンロードしてきます。
ダウンロードしてきたjunitはjunit-4.18.jarとかバージョン名がついているので、
junit.jarという名前にかえておきます。
junitはjparsecのlibフォルダに格納しておきます。
では、eclipseがわの準備を行ってみましょう。
Eclipseに新規にJavaプロジェクトを作成します。
ファイルメニューのインポートで先ほど解凍してできたフォルダを選択し、それをプロジェクトのsrcディレクトリを指定してとりこみます。
srcフォルダは、4つあります、本体用、本体test用、example用、exampleテスト用
インポート直後は
まだ、プロジェクトのビルドパスにjarが登録されていませんので、コンパイルエラーになっています。
そこで、ビルドパスの設定でparsecのlibフォルダ内のjarをすべて登録します。
コンパイルエラー表示はほぼ消えます。
が、1カ所だけAllTest?クラスでエラーになっています。
それは、作者がライブラリをあげたくないからだと build.xmlの80行目に明記してありました。
こんな感じ、
AllTests uses jtc, which is an extra dependency that I don't want to upload.
おそらく、Androidのソースを流用したコードだから、著作権の問題であげれないとでも思ったのでしょうか。
それはさておき、
build.xmlには、このクラスのみ除外してコンパイルする記述がありました。
要するにいらないんです。この
だからbuild.xmlをいじくりたくなかったので、つぎのようにクラスを書き換えておきました。
package org.codehaus.jparsec; //import org.openqa.jtc.junit.TestSuiteBuilder; import junit.framework.TestSuite; /** * * @author benyu */ public class AllTests extends TestSuite { public static TestSuite suite() { //return TestSuiteBuilder.suite(AllTests.class); return null; } }
build.xmlがあるので、toolは、ソースが公開されていないみたいなので、開発元の方用のantタスクかもしれません。それ以外はコンパイルできました。
exampleのテストケースをみると使い方が書いてありました。
package org.codehaus.jparsec.examples.sql.parser;
RelationParserTest?
public void testSelect() {
SQLの問い合わせ文
select distinct 1, 2 as id from t1, t2
が下記のようにクラスの構造に解析されているのを確認しているテストコードが書かれていました。
Parser<Relation> parser = RelationParser.select(NUMBER, NUMBER, TABLE); assertParser(parser, "select distinct 1, 2 as id from t1, t2", new Select(true, Arrays.asList(new Projection(number(1), null), new Projection(number(2), "id")), Arrays.asList(table("t1"), table("t2")), null, null, null));
http://jparsec.codehaus.org/jparsec2+Tutorial
http://d.hatena.ne.jp/taichitaichi/20071008/1191808121
http://www.lab2.kuis.kyoto-u.ac.jp/~hanatani/tmp/Parsec.html
すべて正規表現でまかなうことは可能ではないということなのか 文字列は正規表現でマッチングできるが、さらにそれをトークンとして構造的なパターンマッチを行うのが構文解析なのだろうか?
それとも、正規表現を線形的なパターンマッチだとあえて言うならば、構文解析は木という平面的な解析だというのだろうか?
構文解析の定義はScala言語のcase match に似ている
Scalaには構文解析ツールとして、パーサコンビネータが標準で用意されている。scala.util.parsing.combinator内に、多くのパッケージ、クラスが定義されている。
多忙な Java 開発者のための Scala ガイド: 電卓を作る、第 1 回
http://www.ibm.com/developerworks/jp/java/library/j-scala08268.html
多忙な Java 開発者のための Scala ガイド: 電卓を作る、第 2 回
http://www.ibm.com/developerworks/jp/java/library/j-scala10248.html
多忙な Java 開発者のための Scala ガイド: 電卓を作る、第 3 回
http://www.ibm.com/developerworks/jp/java/library/j-scala11218.html
構文解析
http://sites.google.com/site/scalamemo/raiburari/parsing
第18回 Scalaとパーザコンビネータ(実装編)
http://itpro.nikkeibp.co.jp/article/COLUMN/20100526/348454/
Scala のパーサコンビネータで罠にはまった
http://d.hatena.ne.jp/thinca/20100119/1263837522
そもそも、トークンとはクラスとして置き換えることができそうだ。
たとえば正規表現でデータをマッチさせて、クラスを生成すればいい。
wikiに掲載されていた一覧をのせておきます。
ANTLR Bison Coco/R GOLD JavaCC Lemon Parser Lex LRgen Rebol SableCC Spirit Parser Framework Yacc
Expression[] arrayLiteral() :{...}{ "[" [expression() ("," expression())*] "]" { ... } } Expression expression() :{...}{...}