Ruby から MapR-DB JSON にアクセスしてみる

「やりますか？どうしますか？とりあえずカレンダー作りますか？」っとか言いながら気づいたら12月になってしまいました。
MapRエンジニア勢が年末にかけて超絶忙しくなったため、もはやアドベントではなく正月の宿題、いや2018年中に終わればいいな(；´∀｀)くらいになりそうですが、初日くらいは間に合わせたい！という気持ちで書いてみます。

ということで、MapR Advent Calendar 2017 の初日になります。

# MapR-DB JSONとは

MapR-DBはMapR社が提供するNoSQLデータベースとなります。
MapR-DBはMapR-DB BinaryとMapR-DB JSONがあり、MapR-DB BinaryのほうはHBase APIを利用してアクセス出来ます。
MapR-DB JSONはMapR社がメインで開発しているOJAI（Open JSON Application Interface : オージェーエーアイ、オーハイと呼ばれることが多い）というAPIでアクセス出来ます。
両者の比較やMapR-DB BinaryとHBaseの比較については別の日にエントリーにしたいと思います。

今回は[こちらの記事](https://community.mapr.jp/integrating-mapr-ruby-getting-started-mapr-db-and-mapr-streams-jruby.html)を参考にしながら、MapR-DB JSONにRubyからアクセスしてみます。

# 準備

Rubyからアクセスといっても、MapR-DB JSON用のバインディングはJava, C, Pythonしか提供されておらず、Rubyからアクセスしたい場合はJRubyを利用し、Javaのライブラリをインクルードする必要があります。
まずはご利用の環境でJRubyのインストールを行って下さい。

私の環境ではrvmをインストールしていたので、こちらで一発です。

```
# rvm install jruby
# rvm use jruby
# ruby -v
jruby 9.1.13.0 (2.3.3) 2017-09-06 8e1c115 OpenJDK 64-Bit Server VM 25.151-b12 on 1.8.0_151-b12 +jit [linux-x86_64]
```

# Let's play

OJAIのAPIのドキュメントは[こちら](https://maprdocs.mapr.com/apidocs/maprdb_json/51/)になります。
これを使って、まずはテーブルを作成し、データを突っ込んだり、編集したりしてみましょう。

```rb
include Java
Dir["/opt/mapr/lib/\*.jar"].each { |jar| require jar }

java_import com.mapr.db.MapRDB
java_import com.mapr.db.Table
java_import org.ojai.Document
java_import org.ojai.DocumentStream
java_import org.ojai.store.DocumentMutation
java_import org.ojai.store.QueryCondition
java_import org.ojai.store.exceptions.DocumentExistsException
java_import org.ojai.types.ODate

java_import java.io.IOException
java_import java.util.Arrays
java_import java.util.Iterator

java_import org.ojai.store.QueryCondition

tableName = "/user/mapr/db/testtable"
table = nil
if (!MapRDB.table_exists?(tableName))
  puts "Table Doesn't exist, creating"
  table = MapRDB.create_table(tableName)
else
  puts "Table exists"
  table = MapRDB.get_table(tableName)
end
```

これを実行するとパス /user/mapr/db/testdb にMapR-DB JSONテーブルが作成されます。

```shell-session
$ ruby test.rb
Table Doesn't exist, creating

$ maprcli table info -path /user/mapr/db/testtable -json
{
        "timestamp":1512113101782,
        "timeofday":"2017-12-01 02:25:01.782 GMT-0500",
        "status":"OK",
        "total":1,
        "data":[
                {
                        "path":"/user/mapr/db/testtable",
                        "numregions":1,
                        "totallogicalsize":0,
                        "totalphysicalsize":0,
                        "totalcopypendingsize":0,
                        "totalrows":0,
                        "totalnumberofspills":0,
                        "totalnumberofsegments":0,
                        "autosplit":true,
                        "bulkload":false,
                        "tabletype":"json",
                        "regionsizemb":4096,
                        "audit":false,
                        "maxvalueszinmemindex":100,
                        "adminaccessperm":"u:mapr",
                        "createrenamefamilyperm":"u:mapr",
                        "bulkloadperm":"u:mapr",
                        "indexperm":"u:mapr",
                        "packperm":"u:mapr",
                        "deletefamilyperm":"u:mapr",
                        "replperm":"u:mapr",
                        "splitmergeperm":"u:mapr",
                        "defaultcompressionperm":"u:mapr",
                        "defaultmemoryperm":"u:mapr",
                        "defaultreadperm":"u:mapr",
                        "defaulttraverseperm":"u:mapr",
                        "defaultwriteperm":"u:mapr",
                        "uuid":"fd99673e-4a43-1425-3851-0db603215a00"
                }
        ]
}
```

テーブルが出来ました。次に何かデータを入れて削除してみましょう
MapR-DB JSONではJSON型のドキュメントを作成し、このドキュメントをテーブルに登録します。
削除の際にはドキュメントやrowkeyを指定して削除するrowを選択出来ますが、ここではrowkeyを使ってみます。

```rb
# 上のコードに続けて

(10..20).each {|id|
  document = MapRDB.new_document
  .set("_id", "jdoe#{id}")
  .set("first_name", "John#{id}")
  .set("last_name", "Doe#{id}")
  .set("dob", ODate.parse("2016-06-23"))
  .set("drink", ["beer", "apple cider", "coffee"])
  .set("level", id)

  table.insert_or_replace(document)
}

# 検証
record = table.find_by_id("jdoe")
puts "jdoe's info #{record.inspect}"
record = table.find_by_id("jdoe15")
puts "jdoe15's info #{record.inspect}"
puts "jdoe15's drink info #{record["drink"]}"
table.delete("jdoe20")
record = table.find_by_id("jdoe20")
puts "jdoe20's info #{record.inspect}"
```

実行してみます。

```shell-session
$ ruby test.rb
Table exists
jdoe's info nil
jdoe15's info {"_id"=>"jdoe15", "dob"=>#<Java::OrgOjaiTypes::ODate:0x1e4d3ce5>, "drink"=>#<Java::ComMaprDbRowcol::DBList:0x4b5189ac>, "first_name"=>"John15", "last_name"=>"Doe15", "level"=>15}
jdoe15's drink info ["beer", "apple cider", "coffee"]
jdoe20's info nil
```
期待通り動いていますね。
おわかりのように格納するデータ型にはStringだけでなくさまざまな型が使えます。
ちなみにODateは[OJAIで定義されているデータ型](http://www.ojai.io/javadocs/latest/org/ojai/types/ODate.html)となります。


更新の際にもtableに対してreplace()やupdate()メソッドを利用することが出来ます。
ここでは[DocumentMutationクラス](http://www.ojai.io/javadocs/latest/org/ojai/store/class-use/DocumentMutation.html)を利用した更新についてご紹介します。

DocumentMutationではドキュメントに対する更新をオブジェクトで持ち、これをテーブルのupdate()に渡してテーブルを更新することが出来ます。
この時、append() や merge()、increment()といったメソッドにより、型を意識した変更を表現することが出来ます。
試してみましょう。

```rb
# 上のコードに続けて

mutation = MapRDB.new_mutation

# 新規カラム追加
mutation.set("father's_name", "Andre")

# 既存カラムへの追記。同一のカラムに対しては最終の更新のみ反映
mutation.append("first_name", " Jonny")
mutation.append("first_name", " Jonathan")

# 既存のカラムでない場合は追加扱い
mutation.append("middle_name", "Smith")

# Array要素の追加
mutation.append("drink", ["fanta"])

# 削除
mutation.delete("last_name")

# Map型のマージ
mutation.merge("other_info", {"car" => "Mustang"})

# 数値のインクリメント
mutation.increment("level", 10)

table.update("jdoe15", mutation)
record = table.find_by_id("jdoe15")
puts record.inspect
puts record["drink"]
```

もはやRubyっぽさが全くないコードになってきましたが、気にしないでいきましょう。
実行してみます。

```shell-session
$ ruby test.rb
Table exists
jdoe's info nil
jdoe15's info {"_id"=>"jdoe15", "dob"=>#<Java::OrgOjaiTypes::ODate:0xa307a8c>, "drink"=>#<Java::ComMaprDbRowcol::DBList:0x6986852>, "first_name"=>"John15", "last_name"=>"Doe15", "level"=>15, "other_info"=>{"city"=>"LA"}}
jdoe15's drink info ["beer", "apple cider", "coffee"]
jdoe20's info nil
#<Java::ComMaprDbRowcol::MutationImpl:0x73a8da0f>
{"_id"=>"jdoe15", "dob"=>#<Java::OrgOjaiTypes::ODate:0x7d898981>, "drink"=>#<Java::ComMaprDbRowcol::DBList:0x21129f1f>, "father's_name"=>"Andre", "first_name"=>"John15 Jonathan", "level"=>25, "middle_name"=>"Smith", "other_info"=>{"car"=>"Mustang", "city"=>"LA"}}
["beer", "apple cider", "coffee", "fanta"]
```

期待通りですね。
遊び終わったらテーブルを掃除しておきましょう。

```rb
# 上のコードに続けて

MapRDB.deleteTable(tableName)
```

確認です。

```shell-session
$ ruby test.rb
...省略...
$ maprcli table info -path /user/mapr/db/testtable -json
{
        "timestamp":1512117965049,
        "timeofday":"2017-12-01 03:46:05.049 GMT-0500",
        "status":"ERROR",
        "errors":[
                {
                        "id":22,
                        "desc":"Requested file /user/mapr/db/testtable does not exist."
                }
        ]
}
```

掃除されていました。

さて、今回はRubyからMapR-DB JSONにアクセスしてみました。
次回はPythonからアクセスしてみる、予定です。