今日はデータの追加をやります。
トランザクションのデータストラクチャー
トランザクションをあらわすデータストラクチャーは次のような形になります。
[ [data-structure-list] [data-structure-list] ... ]
あるいは
[ {data-structure-map} {data-structure-map} ... ]
個々のデータ操作を行うリストかマップを要素に持つリストです。これはデータの追加、更新、削除においてすべて共通する構造ですので、確実に覚えておいたほうが良いです。
データ追加のdata structure
データを追加する際のdata structureは次の二つがあります。
リスト形式の場合:
[:db/add new-entity-id attribute-name attribute-value]
リスト形式の場合、複数の属性に値を設定することはできません。属性1つずつ丹念に心をこめて:db/add
していきます。
マップ形式の場合:
{ :db/id new-entity-id attribute-name attribute-value attribute-name attribute-value attribute-name attribute-value ... }
マップ形式の場合の式は、内部的にはリスト形式に変換されて実行されます。丹念に心をこめて書いていた:db/add
は機械的に実行されるようになります。
一時ID
先に示したリスト/マップの追加用のdata structureでnew-entity-id
のところに、既存のエンティティのIDを指定すると、データの更新になります。したがって、新しいデータを追加する場合は一時IDを利用しないといけません。
一時IDを取得する式は次のとおりになります。
#db/id[partition-name long-value]
上記の式でlong-value
はオプションのため、省略が可能です。しかし、他の新しいエンティティへの参照を登録する必要がある場合には、そのエンティティを参照できるようにするために、long-value
を指定しておいたほうがよいでしょう。
partition-name
はデータが所属するパーティションの名前を指定します。結合を頻繁に行うエンティティ同士は同じパーティションに所属している方がデータアクセスの効率がよくなります。デフォルトでいくつかのパーティションが提供されていますが、当面は:db.part/user
というパーティションを使います。
リスト形式でのデータ追加
チュートリアルのデータ構造を利用して、サンプルのデータを登録します。
登録するデータは次のようなデータです。
エンティティ:community
属性名 | 内容 |
---|---|
:community/name |
"Hoge" |
:community/url |
"http://localhost:8000" |
:community/category |
["foo", "bar", "baz"] |
:community/type |
:community.type/twitter |
:community/orgtype |
:community.orgtype/community |
:community/neighborhood |
下記を参照 |
エンティティ:neighborhood
属性名 | 内容 |
---|---|
:neighborhood/name |
"Bar" |
:neighborhood/district |
下記を参照 |
エンティティ:district
属性名 | 内容 |
---|---|
:district/name |
"Foo" |
:district/region |
:region/ne |
上記の内容を追加するトランザクションデータは次のようになります。
datomic-tutorial8-add-as-list.edn
[ [:db/add #db/id[:db.part/user -1] :district/name "Foo"] [:db/add #db/id[:db.part/user -1] :district/region :region/ne] [:db/add #db/id[:db.part/user -2] :neighborhood/name "Bar"] [:db/add #db/id[:db.part/user -2] :neighborhood/district #db/id[:db.part/user -1]] [:db/add #db/id[:db.part/user -3] :community/name "Hoge"] [:db/add #db/id[:db.part/user -3] :community/url "http://localhost:8000"] [:db/add #db/id[:db.part/user -3] :community/category "foo"] [:db/add #db/id[:db.part/user -3] :community/category "bar"] [:db/add #db/id[:db.part/user -3] :community/category "baz"] [:db/add #db/id[:db.part/user -3] :community/orgtype :community.orgtype/community] [:db/add #db/id[:db.part/user -3] :community/type :community.type/twitter] [:db/add #db/id[:db.part/user -3] :community/neighborhood #db/id[:db.part/user -2]] ]
これを流し込むコードは次のようになります。
ClassLoader loader = getClass().classLoader def countQuery = '[:find ?c :where [?c :community/name]]' @Test void 新規データをtransactionDataStructureのdbAddで追加() { def url = loader.getResource('datomic-tutorial8-add-as-list.edn') assert url != null def tx = DatomicUtil.from(url) def dbAfter = connection.transact(tx[0]).get().get(Connection.DB_AFTER) def before = Peer.query(countQuery, db).size() def after = Peer.query(countQuery, dbAfter).size() assert after == before + 1 }
追加前のDatabase
であるdb
と追加後のDatabse
のdbAfter
に対して[:find ?c [?c :community/name]]
を実行すると、dbAfter
の方がレコード数が1件多いことが想定されますので、このようなassert
になっています。
マップ形式でのデータ追加
リスト形式で追加したのと同じようなデータをマップ形式で追加するトランザクションデータは次のようになります。
datomic-tutorial8-add-as-map.edn
[ {:db/id #db/id[db.part/user -1], :district/name "Foo", :district/region :region/ne} {:db/id #db/id[db.part/user -2], :neighborhood/name "Bar", :neighborhood/district #db/id[db.part/user -1]} {:db/id #db/id[db.part/user -3], :community/name "Hoge", :community/url "http://localhost:8000", :community/category ["foo" "bar" "baz"], :community/orgtype :community.orgtype/community, :community/type :community.type/twitter, :community/neighborhood #db/id[:db.part/user -2]} ]
実際にこれを流し込むコードは先ほどのコードと変わりません。読み込むリソースが異なるだけです。
リスト形式のdata structureをAPI経由で追加する
上記のサンプルではedn形式でデータを追加する方法を書きましたが、DatomicではJava APIが提供されています。
@Test void 新規データをdbAddを用いてAPI経由で追加する() { def tempId = {long id -> Peer.tempid(':db.part/user', id) } def dbAdd = ':db/add' def list = [ //districtのデータ [dbAdd, tempId(-1), ':district/name', 'Foo'], [dbAdd, tempId(-1), ':district/region', ':region/ne'], //neighborhoodのデータ [dbAdd, tempId(-2), ':neighborhood/name', 'Bar'], [dbAdd, tempId(-2), ':neighborhood/district', tempId(-1)], //communityのデータ [dbAdd, tempId(-3), ':community/name', 'hoge'], [dbAdd, tempId(-3), ':community/url', 'http://localhost:8000'], [dbAdd, tempId(-3), ':community/category', '["foo", "bar", "baz"]'], [dbAdd, tempId(-3), ':community/orgtype', ':community.orgtype/community'], [dbAdd, tempId(-3), ':community/type', ':community.type/twitter'], [dbAdd, tempId(-3), ':community/neighborhood', tempId(-2)] ] def dbAfter = connection.transact(list).get().get(Connection.DB_AFTER) def before = Peer.query(countQuery, db).size() def after = Peer.query(countQuery, dbAfter).size() assert after == before + 1 }
単純にデータ操作をするリストのリストを作って、Connection#transact(List)
に渡すだけです。一時IDはPeer#tempid(String, long)
あるいはPeer#tempid(String)
により作成できます。
マップ形式のdata structureをAPI経由で追加する
リストでできることはマップでもできます。
@Test void 新規データをAPIから追加する() { // テンポラリーIDを発生させるパーティション def tempId = {long id -> Peer.tempid(':db.part/user', id) } //追加データ def lists = [ [//districtのデータ ':db/id': tempId(-1), ':district/name': 'Foo', ':district/region': ':region/ne'], [//neighborhoodのデータ ':db/id': tempId(-2), ':neighborhood/name': 'Bar', ':neighborhood/district': tempId(-1)], [//communityのデータ ':db/id': tempId(-3), ':community/name': 'Hoge', ':community/url': 'http://localhost:8000', ':community/category': ['foo', 'bar', 'baz'], ':community/orgtype': ':community.orgtype/community', ':community/type': ':community.type/twitter', ':community/neighborhood': tempId(-2)] ] def dbAfter = connection.transact(list).get().get(Connection.DB_AFTER) def before = Peer.query(countQuery, db).size() def after = Peer.query(countQuery, dbAfter).size() assert after == before + 1 }
リスト形式では:db/add
から始まるリストでしたが、マップ形式では':db/id': Peer.tempid(':db.part/user')
を含むマップでデータを追加できます。
データ操作のチュートリアルが微妙にわかりづらかったので、データの追加を調べるだけで1日に費やせる時間を使ってしまいました(´・ω・`)
次回はデータの更新・削除をやりたいと思います。