mike-neckのブログ

Java or Groovy or Swift or Golang

datomicチュートリアル5日目 #datomic

今日は関数の話です

セットアップ辺りのメソッドは前回掲載されたものを使っています。

variable type
db datomic.Database

上記は断りもなく出てきます。ご了承ください。


関数

RDBMSの各種ミドルウェアでは組み込みの関数が提供されていますが、datomicも例外ではありません。関数を記述する際の書式は次のとおりになります。

[(function arguments*) output-binding]

これは関数functionarguments*に対して適用して、その結果をoutput-bindingに拘束します。

例えば、java.lang.String型の変数(?a)のメソッドcompareToを別のjava.lang.String型の変数(?b)に適用して、結果(java.lang.Integer)を変数(?r)に拘束する関数の書式は次のようになります。

[(.compareTo ?a ?b) ?r]

なお、チュートリアルドキュメントによれば、java.langで定義されている関数およびclojure.langで定義されている関数は特に断りもなく利用可能であるとのことです。

では、実際にサンプルコードを動かしてみます。

@Test
void 関数を使う() {
    def results = Peer.query($/[
:find
    [?name ...]
:where
    [?c :community/name ?name]
    [(.compareTo ?name "C") ?fun-result]
    [(< ?fun-result 0)]]/$ as String, db)
    def expected = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B'].collect {
        it as char
    }
    LOG.debug "[${test.methodName}] results-size: [${results.size()}]"
    results.each {String name ->
        assert name.charAt(0) in expected
    }
}

ここで記述されている関数では次のように評価されます。

  1. :community/nameに拘束された文字列?namecompareToメソッドを文字列"C"に対して適用して、結果を?fun-result(java.lang.Integer型)に拘束する
  2. ?fun-resultの比較演算<を数値0に対して適用する。その結果java.lang.Booleanが返ってくるので、それがtrueとなるものが抽出される

String#compareTo(String)は辞書順で比較してメソッドを持っている方のオブジェクトが辞書順で先にある場合はマイナスの値、同じであれば0、辞書順であとにある場合はプラスの値が返されます。したがって、戻される?nameの値は0 1 2 3 4 5 6 7 8 9 A Bから始まる文字列であることが予想できます。そのため、戻された?nameの最初の1文字目は上記の予想されるcharのリストの中に含まれることをassertしています。

実行結果は次の通りで、すべてのassertを通過します。

results-size: [25]

全文検索

スキーマの定義によって、全文検索インデックスを貼ってある要素に対しては全文検索をかけることが可能です。サンプルのスキーマ定義では、:community/nameおよび:community/categoryに対して全文検索インデックスが作成されているとのことで、これらに対してはfulltextなる関数を適用することが可能です。

fulltext関数の戻り値は、検索がヒットしたエンティティと要素とのタプルが返されます。

では、:community/nameに対して全文検索するサンプルコードを試してみます。

@Test
void 全文検索() {
    def results = Peer.query($/[
:find
    ?name
    ?db-id
:where
    [(fulltext $ :community/name "Wallingford") [[?db-id ?name]]]]/$, db)
    assert results.size() == 1
    LOG.debug "[${test.methodName}] results-size: [${results.size()}]"
    def vec = results[0]
    assert vec[0].contains('Wallingford')
    LOG.debug "[${test.methodName}] name:[${vec[0]}], db-id?: [class: [${vec[1].getClass()}], value: [${vec[1]}]]"
}

実行するとすべてのassertをパスして、次のように表示されます。

results-size: [1]
name:[KOMO Communities - Wallingford], db-id?: [class: [class java.lang.Long], value: [17592186045605]]

fulltext関数で返されるエンティティはjava.lang.Longの数値なので、初日の辺りにdb.entity(Long)EntityMapを得たような操作ができるものと思われます。

早速試してみましょう。

@Test
void fulltext関数が何を返しているのか() {
    def results = Peer.query($/[
:find
    ?obj
:in
    $
    ?keyword
:where
    [(fulltext $ :community/name ?keyword) [[?obj]]]]/$,
                db, 'Wallingford')
    assert results.size() == 1
    def vec = results[0]
    LOG.debug "[${test.methodName}] result: [class: ${vec[0].getClass()}, value: [${vec[0]}]]"
    EntityMap entity = db.entity(vec[0])
    LOG.debug "[${test.methodName}] entity: [value: [${entity.get(':community/name')}]]"
}

実行結果はこのとおりです。

result: [class: class java.lang.Long, value: [17592186045605]]
entity: [value: [KOMO Communities - Wallingford]]

見事、予想通りでした。

全文検索の検索キーワードをパラメーターで渡す

それでは、4日目の復習と5日目のまとめ。

全文検索のキーワードはクエリの中に仕込むのではなく、パラメーターで与えたほうが、やりやすいというわけで、全文検索のキーワードをパラメーターで与えるクエリーを試します。

@Test
void 全文検索のキーワードをパラメタで渡す() {
    def results = Peer.query($/[
:find
    ?name
    ?category
:in
    $
    ?type
    ?search
:where
    [?c :community/name ?name]
    [?c :community/type ?type]
    [(fulltext $ :community/category ?search) [[?c ?category]]]]/$ as String,
                db, ':community.type/website', 'food')
    assert results.size() == 2
    results.each {vec ->
        assert vec[1].contains('food')
        LOG.debug "[${test.methodName}] name: [${vec[0]}], category: [${vec[1]}]"
    }
}

このクエリーのパラメーターは

  • ?type : :community/typeの絞り込み条件
  • ?search : fulltext:community/categoryに対する検索キーワード

となっています。

取得するものが、:community/name:community/categoryなので、とりあえず取得された:community/categoryに対して検索キーワードを含む文字列が返されていることでassertしています。

実行結果は次のとおり

name: [Community Harvest of Southwest Seattle], category: [sustainable food]
name: [InBallard], category: [food]

このように、food:community/categoryに含まれているものが返ってきました。


以上。

明日はクエリールール、明後日はdatomicの目玉機能である時間軸とどう相対していくかを扱う予定です。