今日はデータの追加をやります。

トランザクションのデータストラクチャー

トランザクションをあらわすデータストラクチャーは次のような形になります。

[ [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]

リスト形式の場合、複数の属性に値を設定することはできません。属性１つずつ丹念に心をこめて :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 = [ [dbAdd, tempId(- 1 ), ':district/name' , 'Foo' ], [dbAdd, tempId(- 1 ), ':district/region' , ':region/ne' ], [dbAdd, tempId(- 2 ), ':neighborhood/name' , 'Bar' ], [dbAdd, tempId(- 2 ), ':neighborhood/district' , tempId(- 1 )], [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から追加する() { def tempId = { long id -> Peer.tempid( ':db.part/user' , id) } def lists = [ [ ':db/id' : tempId(- 1 ), ':district/name' : 'Foo' , ':district/region' : ':region/ne' ], [ ':db/id' : tempId(- 2 ), ':neighborhood/name' : 'Bar' , ':neighborhood/district' : tempId(- 1 )], [ ':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日に費やせる時間を使ってしまいました(´・ω・｀)

次回はデータの更新・削除をやりたいと思います。