今日はデータの更新をやります。
データ更新のためのdata structure
Datomicではデータの更新用のクエリーというかdata-structureというのはありません。
前回紹介したデータを追加するときのdata-structureをそのまま利用します。
具体的に書くと次のようになります。
リスト形式
[ [:db/add entity-id attribute-name value] ]
あるいは
マップ形式
[ { :db/id entity-id, attribute-name value, attribute-name value ... } ]
データ追加の場合との違い
データを追加する場合は、上記のentity-id
の部分に一時ID(#db/id[:db.part/user long-value]
)を用いていました。
データを更新する場合は、上記のentity-id
に実際のIDを指定して実行します。
テストデータ
データ更新を試すために、今回は次に示すレコードを追加します。
:community/name |
:community/orgtype |
:community/category |
---|---|---|
Foo | :community.orgtype/commercial |
test |
Hoge | :community.orgtype/commercial |
test, kudoh |
Bar | :community.orgtype/commercial |
test, drink, alcohol |
今回追加したレコードは:community/orgtype
が:community.orgtype/commercial
であり、:community/category
の中にtest
が必ず含まれているので、次のクエリーで検索をすると、追加したレコード3件だけが必ず取得できます。
[ :find ?name ?c :where [?c :community/name ?name] [?c :community/orgtype :community.orgtype/commercial] [?c :community/category "test"]]
なお、具体的に追加したデータはgistにおいてあります。
リスト(:db/add
)形式でデータを更新する
まずは:db/add
のdata-structure形式でデータを更新してみます。
static String QUERY = $/[ :find ?name ?c :where [?c :community/name ?name] [?c :community/orgtype :community.orgtype/commercial] [?c :community/category "test"]]/$ static String COUNT = $/[ :find [?name ...] :where [?c :community/name ?name]]/$ @Test void データをdata_structureのdb_addで更新する() { //@Beforeで追加したテストデータを取得する def results = Peer.query(QUERY, db) //@Beforeで追加したテストデータは3件 assert results.size() == 3 results.each { LOG.debug "[${test.methodName}] name: [${it[0]}], id: [${it[1]}]" } //:community/nameがHogeのもののidを取得する def id = results.findAll { it[0] == 'Hoge'}.collect { it[1] }[0] //:community/nameがHogeのものの:community/orgtypeを:community.orgtype/communityに変更する def dataStructure = "[[:db/add ${id} :community/orgtype :community.orgtype/community]]" LOG.debug "[${test.methodName}] data-structure -> ${dataStructure}" def tx = DatomicUtil.from(new StringReader(dataStructure)) def dbAfter = connection.transact(tx[0]).get().get(Connection.DB_AFTER) //最初と同じ条件でデータを取得する def afterResults = Peer.query(QUERY, dbAfter) //件数は2件になっている assert afterResults.size() == 2 afterResults.each { LOG.debug "[${test.methodName}] name: [${it[0]}], id: [${it[1]}]" } //更新前と更新後で:communityのレコード数が変わってないことを検証 def before = Peer.query(COUNT, db) def after = Peer.query(COUNT, db) assert before.size() == after.size() }
これを実行すると次のようなログが表示されます。
[データをdata_structureのdb_addで更新する] name: [Foo], id: [17592186045653] [データをdata_structureのdb_addで更新する] name: [Bar], id: [17592186045659] [データをdata_structureのdb_addで更新する] name: [Hoge], id: [17592186045656] [データをdata_structureのdb_addで更新する] data-structure -> [[:db/add 17592186045656 :community/orgtype :community.orgtype/community]] datomic.db - {:tid 1, :pid 49009, :phase :begin, :event :db/add-fulltext} datomic.db - {:tid 1, :pid 49009, :phase :end, :msec 0.0956, :event :db/add-fulltext} [データをdata_structureのdb_addで更新する] name: [Foo], id: [17592186045653] [データをdata_structureのdb_addで更新する] name: [Bar], id: [17592186045659]
クエリーの検索条件は:community/orgtype
が:community.orgtype/commercial
で、:community/category
に"hoge"
が含まれているものです。データ更新前では該当するレコードが3件あるのは前述のとおりです。:community/name
が"Hoge"
のエンティティIDを取得してから、次のような:community/orgtype
を:community.orgtype/community
に更新するdata structureを作成します。
[[:db/add 17592186045656 :community/orgtype :community.orgtype/community]]
このdata structureを実行した後のデータベースに対して最初に実行したクエリーを実行します。そしてレコードが2件になっていることが検証されます。最後に更新前後のデータベースから:community
エンティティの全レコード件数を取得して比較します。これが変わってないことから、:community/name
が"Hoge"
のレコードが削除されたわけではないことが確認できます。
マップ(:db/id
)の形式でレコードを更新する
上記と同じ操作を:db/id
形式のdata structureでも実行してみます。
@Test void データをdata_structureのdb_idで更新する() { //@Beforeで追加したテストデータを取得する def results = Peer.query(QUERY, db) //@Beforeで追加したテストデータは3件 assert results.size() == 3 results.each { LOG.debug "[${test.methodName}] name: [${it[0]}], id: [${it[1]}]" } //:community/nameがHogeのもののidを取得する def id = results.findAll { it[0] == 'Hoge'}.collect { it[1] }[0] //:community/nameがHogeのものの:community/orgtypeを:community.orgtype/communityに変更する def dataStructure = "[{:db/id ${id}, :community/orgtype :community.orgtype/community}]" LOG.debug "[${test.methodName}] data-structure -> ${dataStructure}" def tx = DatomicUtil.from(new StringReader(dataStructure)) def dbAfter = connection.transact(tx[0]).get().get(Connection.DB_AFTER) //最初と同じ条件でデータを取得する def afterResults = Peer.query(QUERY, dbAfter) //件数は2件になっている assert afterResults.size() == 2 afterResults.each { LOG.debug "[${test.methodName}] name: [${it[0]}], id: [${it[1]}]" } //更新前と更新後で:communityのレコード数が変わってないことを検証 def before = Peer.query(COUNT, db) def after = Peer.query(COUNT, db) assert before.size() == after.size() }
これを実行すると次のようなログが表示されます。
name: [Foo], id: [17592186045653] name: [Bar], id: [17592186045659] name: [Hoge], id: [17592186045656] data-structure -> [{:db/id 17592186045656, :community/orgtype :community.orgtype/community}] datomic.db - {:tid 1, :pid 49164, :phase :begin, :event :db/add-fulltext} datomic.db - {:tid 1, :pid 49164, :phase :end, :msec 0.249, :event :db/add-fulltext} name: [Foo], id: [17592186045653] name: [Bar], id: [17592186045659]
前回でも書きましたが、:db/id
のようなマップ形式のdata structureは内部的には:db/add
に変換されて実行されます。したがって、次のdata structureは先ほど実行したものと同等のものです。
[{:db/id 17592186045656, :community/orgtype :community.orgtype/community}]
APIから実行する
APIから実行する場合も、前回とあまり変わりません。一時IDPeer.tempid(String, long)
を使っていた場所に実際のID(long
)を指定するだけです。
リスト(:db/add
)形式でデータ更新
@Test void データをAPIのdb_addで更新する() { //@Beforeで追加したテストデータを取得する def results = Peer.query(QUERY, db) //@Beforeで追加したテストデータは3件 assert results.size() == 3 results.each { LOG.debug "[${test.methodName}] name: [${it[0]}], id: [${it[1]}]" } //:community/nameがHogeのもののidを取得する def id = results.findAll { it[0] == 'Hoge'}.collect { it[1] }[0] //:community/nameがHogeのものの:community/orgtypeを:community.orgtype/communityに変更する def tx = [':db/add', id, ':community/orgtype', ':community.orgtype/community'] //データ更新 def dbAfter = connection.transact([tx]).get().get(Connection.DB_AFTER) //最初と同じ条件でデータを取得する def afterResults = Peer.query(QUERY, dbAfter) //件数は2件になっている assert afterResults.size() == 2 afterResults.each { LOG.debug "[${test.methodName}] name: [${it[0]}], id: [${it[1]}]" } //更新前と更新後で:communityのレコード数が変わってないことを検証 def before = Peer.query(COUNT, db) def after = Peer.query(COUNT, db) assert before.size() == after.size() }
マップ(:db/id
)形式でデータ更新
@Test void データをAPIのdb_idで更新する() { //@Beforeで追加したテストデータを取得する def results = Peer.query(QUERY, db) //@Beforeで追加したテストデータは3件 assert results.size() == 3 results.each { LOG.debug "[${test.methodName}] name: [${it[0]}], id: [${it[1]}]" } //:community/nameがHogeのもののidを取得する def id = results.findAll { it[0] == 'Hoge'}.collect { it[1] }[0] //:community/nameがHogeのものの:community/orgtypeを:community.orgtype/communityに変更する def tx = [':db/id': id, ':community/orgtype': ':community.orgtype/community'] //データ更新 def dbAfter = connection.transact([tx]).get().get(Connection.DB_AFTER) //最初と同じ条件でデータを取得する def afterResults = Peer.query(QUERY, dbAfter) //件数は2件になっている assert afterResults.size() == 2 afterResults.each { LOG.debug "[${test.methodName}] name: [${it[0]}], id: [${it[1]}]" } //更新前と更新後で:communityのレコード数が変わってないことを検証 def before = Peer.query(COUNT, db) def after = Peer.query(COUNT, db) assert before.size() == after.size() }
まとめ
以上、データの追加と更新はつぎのようにまとめられます。
- データ追加と更新は同じdata structureで実行できる
- データ追加と更新方法にはリスト(
:db/add
)形式とマップ(:db/id
)形式の二通りの方法がある - マップ(
:db/id
)形式では内部的にリスト(:db/add
)に変換されてから実行される - データ追加時は一時IDを指定する
- データ更新時は実際のIDを指定する