データ登録用にgroongaのC APIのgoバインディングを書いてみた
groongaで大量のデータを登録する方法を調べてみた
方法1: loadコマンドの文字列を組み立ててgroongaコマンドの標準入力に流し込む
groongaのデータの登録はチュートリアルのデータのロードにあるようにloadコマンドを使えば出来ます。
外部ファイルから大量のデータを登録するときはどうするのかなと思って調べてみると、 groongaのソースの examples/dictionary/eijiro/ の例では load
コマンドの文字列を組み立てて groonga
コマンドの標準入力に流し込んでいました。
if iconv -f UCS2 -t UTF8 $2 | ${base_dir}/eijiro2grn.rb | groonga $1 > /dev/null; then
echo "eijiro data loaded."
fi
方法2: groongaのC APIを使う
この方法はお手軽ですが、エラー処理が難しそうと重い、さらに調べてみると、groongaのC APIを使ってデータ登録する例を見つけました。
C言語でGroongaのAPIを使う方法 - CreateField
go言語用のライブラリを作ってみました
折角なのでCのライブラリのgo言語バインディングを作る練習を兼ねてgo言語用のライブラリを作ってみました。
テーブルとカラムを作ってレコードを1件登録するサンプルコード
OSX + homebrewという環境で試しました。
brew install groonga
でgroongaをインストールして、以下の手順で実行します。
go get github.com/hnakamur/cgoroonga
cd $GOPATH/src/github.com/hnakamur/cgoroonga/examples/add_record
go build
./add_record
Wikipedia日本語版の記事データを登録するサンプルコード
データファイルは jawiki dump progress on 20150422 から以下の4つのファイルをダウンロードしました。
- jawiki-20150422-pages-articles1.xml.bz2
- jawiki-20150422-pages-articles2.xml.bz2
- jawiki-20150422-pages-articles3.xml.bz2
- jawiki-20150422-pages-articles4.xml.bz2
Wikipediaのデータファイルはxmlをbzip2で圧縮した形式になっているので、Goの標準ライブラリのbzip2とxmlパッケージを使って読み込むようにしています。
サイズの大きいXMLファイルを読み込んで処理するときにおすすめの方法が Parsing huge XML files with Go - david singletonで紹介されていたので、それを真似しました。ありがとうございます!
cd $GOPATH/src/github.com/hnakamur/cgoroonga/examples/import_wikipedia
go build
./import_wikipedia jawiki-20150422-pages-articles1.xml.bz2
のように実行します。
Cライブラリのgoバインディングを書くときのtips
基本的には
を読めばOKなのですが、ハマった点をメモしておきます。
import “C"の上に空行を入れないように注意
たとえば
https://github.com/hnakamur/cgoroonga/blob/5eb6e092c4f6d53257b499cffacd51b8dd194ca3/column.go#L7
で import "C"
の上に空行を入れて go build
を実行すると以下の様なエラーになります。
$ go build
# github.com/hnakamur/cgoroonga
could not determine kind of name for C.free
could not determine kind of name for C.grn_column_create
could not determine kind of name for C.grn_obj_column
could not determine kind of name for C.grn_obj_flags
could not determine kind of name for C.strlen
Cのマクロはgoから呼べないのでCの関数でラップする
https://github.com/hnakamur/cgoroonga/blob/5eb6e092c4f6d53257b499cffacd51b8dd194ca3/cgoroonga.c のようにマクロをラップしたCの関数を書いて、それをgoから呼ぶようにします。
エラーコードが有るエラーと無いエラーを統一的に扱うようにした
groongaのC APIはほとんどが7.20.21. grn_table — Groonga v5.0.2ドキュメントの grn_table_delete のように grn_rc を返します。
grn_obj *grn_table_create(grn_ctx *ctx, const char *name, unsigned int name_size, const char *path, grn_obj_flags flags, grn_obj *key_type, grn_obj *value_type)
のように grn_rc
を返さないAPIもあります。ドキュメントには明記されていませんが、Cの慣例としてエラーのときはおそらく戻り値が NULL
になるのだと予想します。
https://github.com/groonga/groonga/blob/v5.0.2/lib/db.c#L744-L930 を見るとやはりNULLを返すケースが有りました。
そこで、 https://github.com/hnakamur/cgoroonga/blob/5eb6e092c4f6d53257b499cffacd51b8dd194ca3/error.go のようにエラーコードが有るエラーと無いエラーを全てGoの変数として定義するようにしてみました。
table, err := ctx.TableOpenOrCreate("Articles", "",
grn.OBJ_TABLE_HASH_KEY|grn.OBJ_PERSISTENT, keyType, nil)
if err != nil {
return
}
のようにエラーを常に戻り値で受け取るように統一することができ、見通しのよいコードが実現できました。
まとめ
データ登録用にgroongaのC APIのgoバインディングを書きました。
C APIがエラーコードを返さない場合でもGo側ではエラーを返し if err != nil
というのようにエラーチェックの方式を統一することで、エラー処理の漏れに気づきやすくする事が出来ました。