関数型言語XQueryで大量のXMLを楽にさばく


まえがき

オープンデータ隆盛のこのご時世、意外と XML でデータを公開しているケースは多い。 XML形式で公開されているオープンデータを扱う際に XMLデータベース の一つである BaseX を活用してみた経験についてざっと書いてみる。

公開されている実世界のオープンデータを、事前知識なしで扱うのはなかなか大変だ。ドキュメントが整備されていなかったり、不揃いであったりして、ざっとデータの傾向を見てみないと活用しづらいケースが多々ある。 しかし、

  • 帯域幅の制限もあって一回のアクセスでは十分な量のデータを得られない
  • API があまり作り込まれていないため、欲しい情報をピンポイントで得るための検索ができない

などというのは日常茶飯事だ。 そこで試しにちょっとしたスクリプトを書いて、まとまった量のデータをマイルドな周期でダウンロードすることになるけれど、 書き捨てのスクリプト一々書くのは億劫だ。 期待通りのデータがちゃんと取得できていなければやりなおし、試行錯誤を繰り返すことにもなる。 XML を ロードして XPath で解析して、RDBMS に格納して…を繰り返していると、 こんなしょうもないコードを書いている暇はない!となる。

そこで偶々別の仕事をしていて思いついたのは、 APIのエンドポイントから取得して必要な形式に変換する前の、テンポラリーな XMLの格納場所として XMLデータベース (BaseX) を使うのはどうか、ということだ。 実際 BaseX を少し触ってみると、 XQuery が案外リッチで書いていて楽しかったり、同時に大量の XML ファイルを解析できて楽だ。 また JDBC を使って MySQL とかに接続できるので次のフェーズとの相互運用性も悪くなかったりと、案外良いんじゃないか? という気になってきた。 幸い、 BaseX の開発はいまのところ活発で、ここ2年でも色々と改善されているようだ。

そういうわけで、今回は BaseX のさわりをざっくり紹介する。

起動

http://basex.org/products/download/ から BaseX831.zip をダウンロードして展開後、

./bin/basex

で、スタンドアロンモードで起動できる(サーバーモードや GUIモードもあるが割愛)。 DBを操作する様々な コマンド が用意されている。 QUIT か ^D で抜けられるほか、 readline っぽいキーバインドの多くが使えるようになっている。

XQuery

  • XQuery は 多くの部分を XPath と共有している。
  • BaseX は XQuery 3.0 を実装している。
  • XQuery を書くには XQUERY コマンドを使う。CUIだと改行はできず、ワンライナーになる。
  • ワンライナーがきつい場合、 RUNコマンドで XQuery のスクリプトを指定したり、 basexコマンドの引数に与えたりすれば複数行にできる。

XQuery は関数型言語のようなものなので、DBにデータが無くとも何か書くことができる。 XQuery には文字列、数値、XML、列、マップ、第一級の関数など、プログラミング言語にありそうなリテラルは豊富にある。

XQUERY "Hello," || "World"
Hello, World

XQuery は XMLリテラルを持つので、XMLのタグ自体も正しい XQuery の式だ。

XQUERY <hello/>

XQuery で重要かつ厄介なのは 列式 (sequence expresssion) だ。 1,2,3 のようにカンマで区切り、カッコは必須ではない。 他言語の配列と良く似ているが、ネストはできない。 例えば 1,2,(3,4),51,2,3,4,5 と等価だ。 () は空の列で、 ('a', (), 'b', 'c')('a', 'b', 'c') と等価だ。 n to m で数列も生成できる。

XQUERY (1 to 10)
1
2
...
10

列は、FLWOR 式 でイテレートできる。 FLWOR 式とは for, let, where, order by, return からなる式のことで、 XQuery を特徴づけるものだ。

XQUERY for $i in (1,2,3, (4, 5), 6) where $i<4 return $i

などと書けば、

1
2
3

が返ってくる。 まあ、list comprehension の一種といえばそうかもしれない。

return 以外は必須ではない。 たとえば let 文で変数に式を束縛して何かを計算、というようなこともできる。

XQUERY let $elm := <root> <tag1>hello</tag1>, <tag2>world</tag2> </root> return $elm/tag1
<tag1>hello</tag1>

XQuery は XPath を包含しており、 XPath 式の結果を FLWOR でイテレートできる。 ここで、html:parsefetch:text は BaseX の豊富な XQuery Module の一部だ。

XQUERY let $html := html:parse(fetch:text('http://qiita.com/keigoi')) for $a in $html//a return $a/@href
href="/keigoi"
href="https://github.com/keigoi"
href="https://twitter.com/keigoi"
...

実のところ、これがもっとも XQuery らしい使い方だと思う。

forlet は、束縛をカンマで区切ると連続して書ける。

XQUERY for $i in (1,2), $j in (4,5) return $i+$j
5
6
6
7

関数は第一級である(function と書くのは面倒だけど)。

let $succ := function($x) { $x+1 } return (for $i in (10,20,30) return $succ($i))
11
21
31

データベースの構造とドキュメントの追加

CREATE DB testdb

で、testdb という名前の DB が作成される。

BaseX の データはフラットなファイルシステムのようなもので、ディレクトリやファイルモードといった概念はない。 ここに、大量の XML ドキュメント を置いていくことになる。

  • ドキュメントを追加するには、ADD コマンド か、 XQuery の db:add 関数 を使う。どちらも BaseX 固有のものだ。
XQUERY db:add('testdb', <hello>こんにちは世界</hello>, 'hello.xml')

とすれば、 test データベースに hello.xml を追加できる。

ドキュメントの一覧は LIST コマンドで確認できる。

> LIST testdb
Input Path  Type  Content-Type     Size  
---------------------------------------
hello.xml   xml   application/xml  3     

Resources.
>

ドキュメントの取得

XQUERY doc('testdb/hello.xml')/hello
<hello>こんにちは世界</hello>

複数のドキュメントの一括取得

XQUERY collection('testdb/path1/path2')

path1/path2 以下のドキュメントを一度に取得できる。
実のところ、この機能のおかげで大量のXMLドキュメントを解析できるといっても過言ではない。BaseX の構造自体はフラットだが、このようにリソースのパス毎にまとめて扱えるので、パスの工夫次第で色々と工夫できる。

あとがき

やや中途半端だがこの記事はこれで終わり(何度か追記しているが)。 気が向けば続きを書こうと思う。
あとは雑多なことを書いておく。

なぜこれを書いた

某所で、ひょんなことから バックエンドにXMLデータベースを使っている Webアプリを垣間みる機会があった。 最初は eXist だったが 今は BaseX を使っている。 どこかの業者が作ったようだけど、 XQuery をふんだんに使うというよりも、 XSLT を沢山使って HTML に変換していく、という悪夢のような構造をしていた。

おお、XMLデータベース...

  • XML はデータ授受のためのフォーマットであり、DBで管理するなんて馬鹿げている…
  • 昔、 Webアプリの全てを XML と XSLT で書けば、RDBとのインピーダンスミスマッチがなくてサイコー!という人々は居たけれども、XSLT で処理を書くなんて悪夢以外の何物でもないし、やってられない
  • そもそも XML なんて冗長きわまりないし、Javaとか流行ってた頃の全然イケてないレガシーな技術。 クールな tech guy なら RDBMS や NoSQL が基本だし、 フォーマットといえば JSON とか YAML でしょ

などと思ったものだった。

気に入ったこと、厄介なこと

しかし今回 XQuery を触ってみて楽しかったのと、 XQuery が XPath 書き放題なスクリプト言語として使えるのとで、気に入りつつある。また、 HTTPサーバーに接続して HTMLをパースして…ということも可能なので、オープンデータの取得から MySQL への格納まで全てを BaseX で賄うのも可能…のようにも思えてくる。 Python に飽きてきた私には朗報だ!

ただ、そうは問屋が下ろさないのがこのネタのネタたる所以だ。やはり DBなので、1時間くらいかけて多くのXMLを取得するようなクエリが途中で失敗すると ACID 特性のせいで 途中まで取得したXMLがすべて消え失せたるといったことが起こったし、長時間かかるクエリの間ずっと DB全体がロックされ続けるという問題もある(これは BaseX が成熟していないせいか)。

BaseX には多くのモジュールがあり、とても単なるDBクエリのためだけとは思えない側面がある。 ユニットテストとか。 掘り下げると面白そうな処理系なのだが、英語の壁なのか、あまり使ってる人が目に入ってこない。 日本語で XQUery 3.0 について書かれた記事は皆無だ。 XML 関連技術のネガティブイメージも大きいかもしれない。 しかし少なくとも XQuery はなかなか面白くて使える技術なので、もっと触る人が増えると良いと思う。

その他のトピック

  • JOIN 演算のパフォーマンス
  • RDBMS との接続
  • パーミッション
  • 関数宣言
  • if式
  • 名前空間
  • 例外処理
  • 副作用のマーキング
  • ユーザー定義のモジュール

(以上)