mike-neckのブログ

Java or Groovy or Swift or Golang

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

今日は多重カーディナリティのデータの更新方法です。

テストデータの確認

:community/categoryのカーディナリティはManyです。前々回に準備したテストデータで、:community/nameHogeとなっているデータの:community/categoryの値を再度確認してみます。

実行するクエリは次のとおりです。

[
:find
    ?c .
:where
    [?c :community/name "Hoge"]]

このクエリーでエンティティIDを取得した後に、Database#entity(long)からエンティティに変換して、Entity#get(Keyword)によって:community/categoryを取得します。

@Test
void まずnameがHogeのエンティティのcategoryを確認する() {
    long id = Peer.query(FIND_HOGE_ID, db)
    def entity = db.entity(id)
    entity.get(COMMUNITY_CATEGORY).eachWithIndex {cat, idx ->
        log "category[$idx]: [$cat]"
    }
}

実行すると次のようなログが表示されます。

category[0]: [kudoh]
category[1]: [test]

これを今回は["hoge", "piyo", "mucho"]に変更します。

データの更新

前々回ではデータを更新するトランザクションdata structureは次のような形であることを確認しました。

[
    {:db/id entity-id :attribute/name attribute-value}
]

そこで、:community/categoryに対して次のようなマップ(:db/id)形式のトランザクションdata structureを実行してみたいと思います。

[
    {:db/id id :community/category ["hoge", "piyo", "mucho"]}
]
@Test
void 更新だからdbIdだと思うだろ() {
    // :community/nameがHogeのエンティティIDを取得
    long id = Peer.query(FIND_HOGE_ID, db)
    // :db/id データ構造作成
    def tx = [[':db/id': id, ':community/category': ['hoge', 'piyo', 'mucho']]]
    // トランザクション
    def afterDb = connection.transact(tx).get().get(Connection.DB_AFTER)
    // エンティティ取得
    def after = afterDb.entity(id)
    // 表示
    def categories = after.get(COMMUNITY_CATEGORY)
    categories.eachWithIndex {cat, idx ->
        log "category[$idx]: [$cat]"
    }
    // テスト
    def before = db.entity(id)
    assert before.get(COMMUNITY_CATEGORY).size() < categories.size()
}

実行すると、このようなログが表示されます。

category[0]: [kudoh]
category[1]: [mucho]
category[2]: [piyo]
category[3]: [hoge]
category[4]: [test]

ただ、単純に追加されてしまったようです。

データのやり直し?

マップ(:db/id)によるトランザクションは内部的にリスト(:db/add)形式のトランザクションに変換されているため、データがただ追加されていっただけのようです。

そこで、データのやり直し:db/retractで変更を行えるのではないかと推測してみます。

@Test
void dbRetractは存在するデータを消すだけ() {
    // :community/nameがHogeのエンティティIDを取得
    long id = Peer.query(FIND_HOGE_ID, db)
    // :db/retract
    def tx = [
            [':db/retract', id, COMMUNITY_CATEGORY, 'hoge'],
            [':db/retract', id, COMMUNITY_CATEGORY, 'piyo'],
            [':db/retract', id, COMMUNITY_CATEGORY, 'mucho']
    ]
    // トランザクション
    def afterDb = connection.transact(tx).get().get(Connection.DB_AFTER)
    // エンティティ取得
    def after = afterDb.entity(id)
    // 表示
    def categories = after.get(COMMUNITY_CATEGORY)
    categories.eachWithIndex {cat, idx ->
        log "category[$idx]: [$cat]"
    }
    // テスト
    def before = db.entity(id)
    def cat = before.get(COMMUNITY_CATEGORY)
    assert cat == categories
}

これを実行してみましょう。

category[0]: [kudoh]
category[1]: [test]

データは何も変わっていません(´・ω・`)

これは前回:db/retractで値を間違えた場合は、トランザクションが何も発生していないという結果に終わったことを思い出すと、納得がいきます。つまり、やり直そうとした事実は存在していないので、やり直すことはできないということです。

多重カーディナリティの属性を変更する場合は:db/retract:db/addを組み合わせる

で、最後に正解のやり方ですが、データのやり直し(:db/retract)と追加(リストでもマップでも)を一つのトランザクションで行うことによって、データを書き換えます。

@Test
void カーディナリティが複数の属性を変更する場合はdbRetractとdbIdの組み合わせ() {
    // :community/nameがHogeのエンティティIDを取得
    long id = Peer.query(FIND_HOGE_ID, db)
    // :db/retractと:db/idを組み合わせたトランザクション
    def tx = [
            [':db/retract', id, COMMUNITY_CATEGORY, 'test'],
            [':db/retract', id, COMMUNITY_CATEGORY, 'kudoh'],
            [':db/add', id, COMMUNITY_CATEGORY, 'hoge'],
            [':db/add', id, COMMUNITY_CATEGORY, 'piyo'],
            [':db/add', id, COMMUNITY_CATEGORY, 'mucho']
    ]
    // トランザクション実行
    def afterDb = connection.transact(tx).get().get(Connection.DB_AFTER)
    // エンティティ取得
    def after = afterDb.entity(id)
    // 表示
    after.get(COMMUNITY_CATEGORY).eachWithIndex {cat, idx ->
        log "category[$idx]: [$cat]"
    }
    // テスト
    def before = db.entity(id)
    assert after.get(COMMUNITY_CATEGORY).size() == before.get(COMMUNITY_CATEGORY).size() + 1
}

これを実行すると、次のようなログが表示されます。

category[0]: [mucho]
category[1]: [piyo]
category[2]: [hoge]

当初の目的どおり、:community/categoryを更新出来ました。


次回はスキーマの定義をやります。