今日はデータの更新をやります。

データ更新のための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で更新する() { def results = Peer.query(QUERY, db) assert results. size () == 3 results. each { LOG.debug "[ ${test.methodName} ] name: [ ${it[0]} ], id: [ ${it[1]} ]" } def id = results. findAll { it[ 0 ] == 'Hoge' }. collect { it[ 1 ] }[ 0 ] 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) assert afterResults. size () == 2 afterResults. each { LOG.debug "[ ${test.methodName} ] name: [ ${it[0]} ], id: [ ${it[1]} ]" } 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で更新する() { def results = Peer.query(QUERY, db) assert results. size () == 3 results. each { LOG.debug "[ ${test.methodName} ] name: [ ${it[0]} ], id: [ ${it[1]} ]" } def id = results. findAll { it[ 0 ] == 'Hoge' }. collect { it[ 1 ] }[ 0 ] 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) assert afterResults. size () == 2 afterResults. each { LOG.debug "[ ${test.methodName} ] name: [ ${it[0]} ], id: [ ${it[1]} ]" } 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から実行する場合も、前回とあまり変わりません。一時ID Peer.tempid(String, long) を使っていた場所に実際のID( long )を指定するだけです。

リスト( :db/add )形式でデータ更新

@Test void データをAPIのdb_addで更新する() { def results = Peer.query(QUERY, db) assert results. size () == 3 results. each { LOG.debug "[ ${test.methodName} ] name: [ ${it[0]} ], id: [ ${it[1]} ]" } def id = results. findAll { it[ 0 ] == 'Hoge' }. collect { it[ 1 ] }[ 0 ] 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) assert afterResults. size () == 2 afterResults. each { LOG.debug "[ ${test.methodName} ] name: [ ${it[0]} ], id: [ ${it[1]} ]" } def before = Peer.query(COUNT, db) def after = Peer.query(COUNT, db) assert before. size () == after. size () }

マップ( :db/id )形式でデータ更新

@Test void データをAPIのdb_idで更新する() { def results = Peer.query(QUERY, db) assert results. size () == 3 results. each { LOG.debug "[ ${test.methodName} ] name: [ ${it[0]} ], id: [ ${it[1]} ]" } def id = results. findAll { it[ 0 ] == 'Hoge' }. collect { it[ 1 ] }[ 0 ] 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) assert afterResults. size () == 2 afterResults. each { LOG.debug "[ ${test.methodName} ] name: [ ${it[0]} ], id: [ ${it[1]} ]" } def before = Peer.query(COUNT, db) def after = Peer.query(COUNT, db) assert before. size () == after. size () }

まとめ

以上、データの追加と更新はつぎのようにまとめられます。