日曜プログラミング

休日趣味でやってるプログラミング関連記事をダラダラと

datomic チュートリアルを試す(3) - 実体の属性値をクエリで取る

前回、datomic でのデータ定義の考え方と実体の属性値をどうやって取得するかまでやってみた。記事はこちら。
datomic チュートリアルを試す(1) - 導入・DB 作成・最初のクエリ実行 - 日曜プログラミング

今回公式チュートリアルで該当する原文項目は以下。

  • Querying for an attribute's value
  • Querying for multiple attributes' values
  • Querying by attribute values

前回見た人は途中こう思ったかもしれない。「あれ、クエリは?」と。
自分もそう思ってたら次の項目だったw

属性値をクエリで取る(Querying for an attribute's value)

前回やったコミュニティ名一覧を取るコードを再掲する。

(map #(->> (first %) (d/entity (d/db cn)) :community/name) result)

これとほぼ同等の事を行うクエリと実行結果。

> (d/q '[:find ?c ?n :where [?c :community/name ?n]] (d/db cn)))

#{[17592186045470 "Beach Drive Blog"]
[17592186045603 "KOMO Communities - View Ridge"]
  ; ... 省略 ...
[17592186045575 "Jefferson Park Alliance"]
[17592186045472 "Beacon Hill Alliance of Neighbors"]
}

最初にやった :community/name を持つ entity 全てを取るクエリ。

(d/q '[:find ?c :where [?c :community/name]] (d/db cn))

最初のクエリから変わっているのは ?n が妙な所に入ったと言うだけ。

多分これの違いはもう少しコード寄りの言葉で理解した方が良さそうなので書く。

  • 最初のクエリ(?c だけの方)
    • 「属性で :community/name を持つ実体 ?c を探せ」
  • 次のクエリ(?n がついた方)
    • 「属性で :community/name の値が ?n である実体 ?c を探せ」

?の後に続くのはいわゆるクエリ変数のようなもので、どうもこの ? の個数分結果として返されるようだ。多分何もバインドされないと全部返すような動きになるんだろう。
この結果なら map last してやればコミュニティ名だけ抜き出せる。

複数の属性値をクエリで取る(Querying for multiple attributes' values)

こうする。

; コミュニティ名と URL を取得する
> (d/q '[:find ?n ?u
         :where
         [?c :community/name ?n]
         [?c :community/url ?u]]
       (d/db cn))
#{["Chinatown/International District" "http://www.cidbia.org/"]
  ["All About Belltown" "http://www.belltown.org/"]
  ; ... 省略 ...
  ["Magnuson Environmental Stewardship Alliance" "http://mesaseattle.org/"]}

変数らしき ? を増やしゃいいみたい。
ついでに一つ前で entity id 要らないよなあと思ってたがこう書けば特に entity id は出ないみたい。

もういっちょ。これが暗黙の JOIN の例なのかな。

; コミュニティ "belltown" のカテゴリを取得する
> (d/q '[:find ?e ?c
         :where
         [?e :community/name "belltown"]
         [?e :community/category ?c]]
       (d/db cn))
#{[17592186045476 "events"] [17592186045476 "news"]}

ここでまた entity id を取るようにしてるが、その id が同じなのに注目。
:community/name が "belltown" の実体に属してる category が返されている事が分かる。

属性値「で」クエリを実行する(Querying by attribute values)

SQL で言う WHERE field_name = 'value' みたいなの。

; コミュニティのタイプが twitter なコミュニティ名を取得する
> (d/q '[:find ?n
         :where
         [?c :community/name ?n]
         [?c :community/type :community.type/twitter]]
       (d/db cn))
#{["Discover SLU"]
  ["Fremont Universe"]
  ["Columbia Citizens"]
  ["Magnolia Voice"]
  ["Maple Leaf Life"]
  ["MyWallingford"]}

さしずめ :community/type がフィールド名で :community.type/twitter が条件として探す値ってところ。どちらも Clojure 構文上ではキーワードだが、前者は datomic の属性名であり、後者は固定の値であるのに注意するくらいかな。

参照関係にあるもののクエリ(Querying across references)

例として、地域(region)を条件にして該当するコミュニティ全てを探すにはどうすれば良いか?と言うのが例題として出されてる。

しかし、データ定義を見返すと地域は region エンティティに :district/region 属性があるだけで community エンティティにはない。

だが、:community/neighborhood 属性は neighborhood エンティティを参照しており、更にその :neighborhood/district 属性は district エンティティを参照している。で、district エンティティには :district/region 属性がある。

なので、こう書くと取得可能。

; 北西(:region/ne)にあるコミュニティ名を取得する
> (d/q '[:find ?c_name
         :where
         [?c :community/name ?c_name]
         [?c :community/neighborhood ?n]
         [?n :neighborhood/district ?d]
         [?d :district/region :region/ne]]
       (d/db cn))
#{["KOMO Communities - U-District"]
  ["Maple Leaf Community Council"]
  ["KOMO Communities - View Ridge"]
  ["Hawthorne Hills Community Website"] 
  ...}

上記は北西のみだったが、コミュニティ毎の方角を全部出ししたい場合はこう。

; コミュニティの方角全部出す(?)
> (d/q '[:find ?c_name ?r_name
         :where
         [?c :community/name ?c_name]
         [?c :community/neighborhood ?n]
         [?n :neighborhood/district ?d]
         [?d :district/region ?r]
         [?r :db/ident ?r_name]]
     (d/db cn))
#{["Highland Park Action Committee" :region/sw]
  ["Broadview Community Council" :region/sw]
  ["Georgetown Seattle" :region/s]
  ...}

コメントに(?)を付けているのはそもそも方角ってどこ基準なんだろうとふと思ったから。ま、それはさておき次へ行く。

あれ、:db/ident って挟まないとダメ?と思いこうしてみたら、

; これは ?r が方角のエンティティそのものを返すのでちょっと違う
(d/q '[:find ?c_name ?r
       :where
       [?c :community/name ?c_name]
       [?c :community/neighborhood ?n]
       [?n :neighborhood/district ?d]
       [?d :district/region ?r]]
     (d/db cn))
{["Georgetown Art Center" 17592186045433]
  ["KOMO Communities - Green Lake" 17592186045434]
  ...}

説明としてはサンプル内のコメント通り。

今のところは参照をたぐり寄せるような JOIN はちょっと面白いかもしれないと思ったくらいかな。

今日はここまで。

datomic チュートリアルを試す(2) - datomic のデータ定義・実体の属性値の取得

前回、インストール・DB作成・スキーマ定義・クエリ実行までやってみた。記事はこちら。
datomic チュートリアルを試す(1) - 導入・DB 作成・最初のクエリ実行 - 日曜プログラミング

また、この記事書いてる途中で作者による Clojure でどう書くかの動画があるのに気づいた。
Writing Datomic in Clojure
まだ動画は見てないが英語が分かれば作者なだけに正確な説明してくれてるんじゃないかと。

前回ではあらかじめチュートリアル用に用意された定義ファイルを読み込んだだけなので今回はそもそもデータ定義ってどうなってるのと言う所ともう少し進んだデータの取得の仕方を見て行きたいと思う。

今回公式チュートリアルで該当する原文項目は以下。

  • Adding a schema
  • Getting an entity's attribute values

そのまま翻訳するわけではないが、元の英語の用語やチュートリアルの項目は併記する。また、前回からそうだがチュートリアルを上からなぞってはいないので悪しからず。

datomic の schema(Adding a schema)

最低限の構成要素

schema の詳細についてはひとまず置いといて、最初に以下の事については覚えておいて欲しいと。

  • schema は実体(entity)に当てはまる属性名(attribute's name)のセットを定義する
    • 属性名は内部的には :id/ident と呼ばれる
  • 属性は名前(name)、タイプ(type)、濃度(cardinality)を決める*1

あと属性としては他にも実体間での値がユニークである*2とか、全文検索インデックスを生成するかどうかなどを決められるらしい。

また、読み込んだサンプルデータにはシアトルのコミュニティ(communities)、隣接地域(neighborhoods)、地方(districts)を表すデータがある。慣例的に、特定の実体を表す属性にはプレフィックスが付けられる。

公式に書いてある属性一覧をこちらに再掲する。プレフィックスがどこに当たるのかはこれで分かるかと。

Attribute Type Cardinality
:community/name String One
:community/url String One
:community/neighborhood Reference One
:community/category String Many
:community/orgtype Reference One
:community/type Reference One
:neighborhood/name String One
:neighborhood/district Reference One
:district/name String One
:district/region Reference One

表を見ると :community/neighborhood や :neighborhood/district などのタイプが Reference になっている。これはこの属性の値(value)は他の実体を参照する事を意味している。

ここでは community と neighborhood、neighborhood と district とを関連付けるのに使われている。ちなみにファイル読込時に実体も読み込まれている。

ここでちょっと RDBMS のデータの持ち方と比較

ここはまるっきり自分の解釈です。自分もまだチュートリアル読み進めてる途中で間違ってる可能性があるので混乱したくない人は次の項目までスキップして下さい。

datomic では実体(entity)はありのままの素のデータで、それ自身はデータ以外に何も持っているわけではない。その実体が何を意味するのかと言うのを、実体とは分離した属性(attribute) というものにヒモ付けると言う感じか。これを datomic 紹介時に言っている datom とか事実(fact)と呼んでいるのかな。datom と fact を分けているのは何かしら意味がありそうだけどここでは置いとく。

対して RDBMS では先にテーブルと言うデータ構造を定義してそこに実データを入れ込む為本質的に実体と属性と言うものは切り離せない。

datomic では実体は既に存在するもので、そこに属性を付けていくイメージになるかな?
RDBMSではテーブル定義と言う箱ありきで、そこにデータを入れると属性が決定されると言うか。

何か Clojure と言うか Lisp に通ずるものがあるな。REPL で 1 と評価したら 1 と返ってくるようなのを entity と指すのかね。
あ、だから value と entity と分けてたのかな?

他の Reference タイプについて

先の表で :community/orgtype や :community/type 属性のタイプも Reference となっていたが、これらは他の列挙された値を参照する為の名前を定義している*3

以下の表にあるように、:community/orgtype や :community/type 属性はキーワード識別子の形で定義されてる。

Reference attribute Enumerated value identifier
:community/orgtype :community.orgtype/community
:community.orgtype/commercial
:community.orgtype/nonprofit
:community.orgtype/personal
:community/type :community.type/email-list
:community.type/twitter
:community.type/facebook-page
:community.type/blog
:community.type/website
:community.type/wiki
:community.type/myspace
:community.type/ning
:district/region :region/nw
:region/n
:region/ne
:region/e
:region/se
:region/s
:region/sw
:region/w

コロン(:)とドット(.)の使い分けの意味合いが気になるがまあとりあえず先へ。

また、公式ではこの後データ読込の方法と最初のクエリ実行にについて書かれてるが、自分は一足先に前回やっているので飛ばす。

実体の属性値を取得する(Getting an entity's attribute values)

ちょっと前回のクエリと実行結果を再掲する。

> ; 前仕込み。transactor 起動しておくか mem プロトコルで読込を済ませとく事。
> (def cn (d/connect "datomic:free://localhost:4334/seattle"))
> (def result (q '[:find ?c :where [?c :community/name]] (db cn)))

> ; クエリ実行
> (q '[:find ?c :where [?c :community/name]] (db cn))
#{[17592186045520] [17592186045518] [17592186045517] [17592186045516] ...(省略)...}

最初結果の意味が分からんかったが、:community/name 属性を持つ実体を返すような感じか。なので、結果の数を数えればコミュニティ全体数が分かると言う解釈が可能。

で、一旦このクエリ結果を result に入れておく。

> (def result (q '[:find ?c :where [?c :community/name]] (db cn)))
result

クエリの実行結果は一見 Clojure の set のように見えるが、

> (type result)
java.util.HashSet

実際は Java のオブジェクト。

このクエリ結果の先頭を取って :community/name の値を返すにはこう。

; 最初に取得した実体のコミュニティ名を取得
> (-> (d/entity (d/db cn) (ffirst result)) :community/name)
"Downtown Dispatch"

ちなみに公式の Java でのサンプルだとこうなっている。

  id = (results.iterator().next()).get(0);
  entity = conn.db().entity(id);
  name = entity.get(":community/name");

またスレッドマクロ最初の結果について補足。

> (d/entity (d/db cn) (ffirst result))
{:db/id 17592186045520}

これも一見単なる Clojure の map に見えるが、

> (type (d/entity (d/db cn) (ffirst result)))
datomic.query.EntityMap

なのでこれまた注意。

Clojure に最適化されてるわけではなく、そこそこ Java である事を意識する必要がありそう。とは言え、HashSet やら Java の Map は Clojure では割と簡単に扱えるのでいいかも。

閑話休題。

結果セットから全てのコミュニティ名が欲しい時はこう。

; 全てのコミュニティ名を取得
(map #(->> (first %) (d/entity (d/db cn)) :community/name) result)
("Downtown Dispatch"
 "Discover SLU"
 ... 省略 ...
 "Greenwood Community Council Discussion"
 "Haller Lake Community Club"
 "Hawthorne Hills Community Website")
nil

何かもうちょい簡単に書ける気がするがそれはさておき、比較用にチュートリアルにある元の Java コードはこちら。

for (Object result : results) {
  entity = db.entity(result.get(0));
  System.out.println(entity.get(":community/name"));
}
// 結果は省略

最初に取得した実体の隣接地域の名前一覧を取得するにはこう。

; 最初に取得した実体のお隣さんのコミュニティ名を全取得
> (map #(->> (first %) (d/entity (d/db cn))
             :community/neighborhood
             :community/name)
       result)
("Downtown"
 "South Lake Union"
 "South Lake Union"
 "South Lake Union"
 "Delridge"
 "Delridge"
 "Delridge"
 ... 省略 ...
 "Greenwood"
 "Greenwood"
 "Greenwood"
 "Greenwood"
 "Haller Lake"
 "Hawthorne Hills")

で、次のチュートリアルの説明だが、何でそんな事できんだ?ってのが良く分からんけど簡単に訳する。

datomic でのリレーションは双方向です。言い換えると、community の :community/neighborhood
属性は community entity と neighborhood entity のどちらからもヒモ付け可能なリレーションを
生成します。neighborhood entity に :neighborhood/community 属性がないにも関わらず、です。
具体的には属性名(Reference attribute)のプレフィックス後の先頭に _ を付加すると逆方向のリレーションを参照できます。

以下が逆リレーションとでも言えばいいのかな、それを使ったサンプル。

; 最初の実体のお隣が隣接するコミュニティ名を取得
> (map :community/name (->> (ffirst result) (d/entity (d/db cn))
                            :community/neighborhood
                            :community/_neighborhood))
("Downtown Seattle Association" "Downtown Dispatch" "KOMO Communities - Downtown")

ちょっと引っかかる事

  • 記事中でも書いたがデータ定義でのコロン(:)とドット(.)の使い分けの意味合いって何だ
  • お隣さんのコミュニティ名全取得の Clojure コードがこれで良いのかちょっと自信なし
    • 同じのが複数出るとか
    • 結果の要素がコミュニティ名の文字列じゃなく、0以上(?)の隣接地域名を持つシーケンスになるんじゃないかとか
  • 最後の Clojure コードも Java の結果と異なるが、Map データだから多分最初の実体が何になるのかという保証がないんだろな
    • ソートをどこに挟み込むかってのは注意する必要があるかも

今日はここまで。
公式見ると分かるけど結構チュートリアル長いんだよね。

*1:集合論で出てくる用語と全く同じかどうかは分からないが、datomic では 1 対 1 か 1 対 n かを定義できる項目としての意味合いっぽい

*2:原文が their values must be unique across entities なのだがまだ良く分からん

*3:何かややこしいけどポインタのポインタみたいな感じか

datomic チュートリアルを試す(1) - 導入・DB 作成・最初のクエリ実行

前振り

使う前に Datalog って何なんだろうと思って先に調べると、datomic のクエリ言語は Datalog をベースとして拡張したものらしく、特に独自に何か名前が付けられているわけではないようだ。
Datomic Development Resources

ちょっと勘違いしてたのだが datomic のクエリ言語を Datalog と呼ぶと思っていたのだが、Datalog 自体は独立した言語らしい。とは言え、書いていくのに名前がないと何かと不便なので datomic 記事内でのクエリ言語は Datalog と書くことにする。

SQL は個人的には JOIN や相関サブクエリ、スカラサブクエリ、CASE 句、WITH 句、GROUP BY、PARTITION BY など RDBMS から一撃で欲しいデータを取るのにはなかなか便利な言語だと思っとります。ベンダー毎の標準規格対応度合いとか NULL のイヤらしさとかあるけどまあそれはそれで。書いてて何となく Lisp っぽいなとも時々思ったりしたりで割と親近感はある。

なので、SQL の便利さは失わずにより便利になってないとあまり覚える気が起きない。Datalog はどうなんだろうか。

まずは最初に挙げたリンクを読んでみる。

  • 文法がシンプル
  • 宣言的
  • 論理プログラミングベース
  • joins are implicit
  • Unification
  • Blanks
  • The implicit data source - $
  • Querying References
  • Rules
  • Aggregates
  • Aggregates Returning a Single Value
  • Aggregates Returning Collections
  • Control Grouping via :with
  • Custom Aggregates
  • Filtering Databases
  • Joining Multiple Versions of the Same Database
  • Filtering on Transaction Attributes
  • Queries and Peer Memory
  • A Warning

ズラっと項目を並べて途中から英語のままになっちゃってるのは何となくでも良く分からんかったからw

こりゃあ大人しくチュートリアルを読みつつ実際にやってみた方が良さそうだ。
自分の中で概念からまるきりハマってない感がすごいある。

事前準備

公式の 下記チュートリルをある程度なぞってみる。
Datomic Development Resources
DB 作成、スキーマ読込、データ初期化、データのクエリや操作について一通り見ていくとの事。

以降、transactor 起動と peer からの transactor 接続確認まで済んでいるものとして進める。それとチュートリアルでは beanshell だけどここではせっかくなので Clojure で試す。英語でもいいから Clojure でのチュートリアルがあれば紹介できるんだけど、見つからなかったので何となくで読み換えていく。

~\bin\repl を使うと Clojure でもいきなり試せるっぽいが、実際使うとなるとこの辺りの初期設定は必ずやる事になるので一度やっておく。と言っても殆ど難しい部分はない。

datomic を Maven ローカルリポジトリに登録

Clojure でと言う事は leiningen にお世話になるので datomic を登録しておく。
~\bin\maven-install があるのでそれを有難く使う。Windows でも中身の mvn コマンドを ~/ で実行すれば登録可能。

結構色々ダウンロードされるが最終的に BUILD SUCCESS が出れば OK。

leiningen プロジェクト作成

チュートリアルお試し用に leiningen プロジェクトを作る。

>lein new app datomic-tut
Generating a project called datomic-tut based on the 'app' template.
>

project.clj に依存関係追加。

(defproject datomic-tut "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [com.datomic/datomic-free "0.8.4218"]]
  :main datomic-tut.core)

leiningen で依存ライブラリ参照コマンド叩いとく。

>lein deps

環境によるだろうが、結構色々ダウンロードされた。

core.clj 編集

Clojure の datomic API を使えるように use しとく。
~\samples\seattle\getting-started.clj で REPL 確認用のコードが置いてあるのでそこから割と使い回せる。

core.clj 先頭の ns マクロに記述。

(ns datomic-tut.core
  (:use [datomic.api :only [q db] :as d]
        clojure.pprint)
  (:gen-class))

transactor 起動

オンメモリ(mem プロトコル)にすれば transactor 要らずなのだが、前回ローカルファイル(free プロトコル)での transactor 動かしてたのでそいつを起動。

>bin\transactor config\free-transactor.properties
'data-dir' property not set, defaulting to 'data' in current directory
'log-dir' property not set, defaulting to 'log' in current directory
Starting datomic:free://localhost:4334/<DB-NAME>, storing data in: data ...
System started datomic:free://localhost:4334/<DB-NAME>, storing data in: data

以後 REPL 上で確認(lein repl でも emacs 経由 nREPL でもお好きなように)。
自分は emacs + clojure-mode + nrepl.el 環境で以後試す。

transactor 接続

最初は Java shell から確認してたが、今回は emacs nrepl 上となるので一応試しておく。

さっき書いた ns は C-c C-n してーの、C-c M-j して REPL 起動。
Clojure の datomic APIを見つつ試す。
サンプルでは in-ns 後のプロンプトは実際には datomic-tut.core が出てるが省略する。

; nREPL 0.2.0alpha
user> (in-ns 'datomic-tut.core)
#<Namespace datomic-tut.core>
> (def uri "datomic:free://localhost:4334/test")
#'datomic-tut.core/uri
> (d/connect uri)
#<Connection {:db-id "test-7f951986-43bd-492a-bb56-d6e9dc655c78", :unsent-updates-queue 0, :pending-txes 0, :next-t 1000, :basis-t 62, :index-rev 0, :index-root {:rev 0, :key "5268debf-cd9a-47c4-ae7c-a8bcae10b8bc"}, :log-root {:rev 2, :key "5268debf-f5a0-4b2f-90e1-2eb189e2ebbe"}}>
> 

うん、繋がった。

DB 削除

ん、いきなり DB 削除?いや、transactor 接続確認で作った test はチュートリアルで使うものではないので削除しておきたかったのです。
API 探すとあった

> (d/delete-database uri)
true

DB 作成

Java Shell でやってたのと意味上は殆ど変わらんけどチュートリアル用の DB である seattle を作る。

> (def uri "datomic:free://localhost:4334/seattle")
#'datomic-tut.core/uri
> (d/create-database uri)
true

スキーマ追加

サンプルスキーマファイルが datomic 展開フォルダの sample 以下にある *.dtm ファイルみたいなので
leiningen プロジェクトフォルダに resources フォルダを作った上でコピー & project.clj の :resources-path に追加して REPL 再起動。

(defproject datomic-tut "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [com.datomic/datomic-free "0.8.4218"]]
  :resource-paths ["resources"]
  :main datomic-tut.core)

チュートリアルではデータ構造の説明が結構続いてるが、そこは飛ばしてとりあえず読み込ませる。
core.clj の先頭 ns マクロに少し追加して C-c C-n。

(ns datomic-tut.core
  (:use [datomic.api :only [q db] :as d]
        clojure.pprint)
  (:require [clojure.java.io :as io])
  (:import datomic.Util)
  (:gen-class))

実際の読み込ませるコードはこう。ns マクロで Java の datomic.Util を import してるのは Util/readAll メソッドの ClojureAPI が見当たらなかった為。

> (with-open [r (io/reader "resources/seattle-schema.dtm")]
    (def schema-tx (.. (Util/readAll r) (get 0))))
#'datomic-tut.core/schema-tx
> (def cn (d/connect "datomic:free://localhost:4334/seattle"))
#'datomic-tut.core/cn
> (def result (d/transact cn schema-tx))
#'datomic-tut.core/result

REPL 上の確認なので def でいちいち返って来たものを格納してるが、実際に作り出すとどんな感じがいいかはまだ良く分からん。

シードデータの追加

直訳だが、要は実データの追加。
これもサンプルファイルがあるので読む。
コード見るとスキーマの追加と殆ど変わらんのでヘルパー関数定義。

(defn read-dtm [f]
  (with-open [r (io/reader f)]
    (->> (.. (Util/readAll r) (get 0))
         (d/transact cn))))

で、読み込む。

> (read-dtm "resources/seattle-data0.dtm")
#<promise$settable_future$reify__5411@11fe48: ...(省略)...>

データベースへの問い合わせ

始めのクエリ

スキーマ定義もデータ読込も無事完了したら次のクエリ発行してみようとあるので試す。
チュートリアルで出てたのはこう。

[:find ?c :where [?c :community/name]]

REPL で実行。

> (q '[:find ?c :where [?c :community/name]] (db cn))
#{[17592186045520] [17592186045518] [17592186045517] [17592186045516] ...(省略)...}

これ何返ってきてるんだろな、って所から良く分からん。次回すっ飛ばしたデータ構造確認してみるか。

今回はここまで。

datomic を試してみる(番外) - SQL Database Storage 化失敗編

元々こっちを最初に試してたのだが、transactor から SQL DB への接続が上手くいってなないので途中で諦めた残骸。
結構な時間ハマって結局上手く行っていない。
誰かが助けてくれるかもと言う期待を込めてここに置いておく。

【失敗】SQL DB を Storage 化する(Provisioning a SQL database)

これをやる手順は以下のようになると最初に書いてある。

  1. SQL DB をセットアップするか、既存のを使う
  2. SQL Table を作成する(datomic_kvs)
  3. SQL ユーザを作成するか既存のを使う
  4. JDBC 接続文字列を取得する

1,3 に関しては自分は既存のを使う方を選択するので割愛。

SQL Table を作成する

最初の手順概要にあった 3 相当。~\bin\sql 以下に各 RDBMS
でセットアップに必要な sql スクリプトがあり、Oracle だと oracle-table.sql を実行。

ここでは 4 については特に触れられてなかったが別に特殊なものでもないんだろう。

評価キーインストール(Install your licence key)

プロパティファイルにキーを入れてね、とあるが、config/samples にあるものを触ればいいのかな。
とりあえず sql-transactor-template.properties を sql-transactor.properties にリネームコピ
ーして以下のエントリを編集。

license-key=(もらった評価キー)

もらった評価キーは 6 行になってるが、バックスラッシュで改行をエスケープしてるっぽいのでそのまま貼り付ける。

【失敗】transactor を起動する(SQL 時)

transactor って何さってのは詳しくは最初の紹介記事リンク先を読んでもらうとして、まだ入れる
前の時点での自分の認識は Storage に対する書込とインデクシングを担当する別プロセスと言うも
の。

で、手順だが、ああ、さっきのプロパティファイルってここで使うのか。
それとさっき飛ばしていた SQL DB とユーザの指定っぽい設定項目もプロパティファイルにあった。
JDBC のドライバクラスの指定もきっと要るので一緒に指定。

################################################################
# See http://docs.datomic.com/storage.html

sql-url=jdbc:oracle:thin:@//localhost:1521/TEST
sql-user=hoge
sql-password=fuga

# The Postgres driver is included with Datomic. For other SQL
# databases, you will need to install the driver on the
# transator classpath, by copying the file into lib/,
# and place the driver on your peer's classpath.
sql-driver-class=oracle.jdbc.driver.OracleDriver

と言うかこの指定って DB やユーザを新たに作成しても一緒なような。
あ、URL やユーザ・パス・JDBC ドライバクラスは自身が使うものに適時置き換えたって下さい。

また、プロパティファイルのコメント見ると書いてあるんだが、transactor 動かすには指定した
RDBMS のドライバを lib 以下に入れてねとあるのでそれに従い ojdbc をコピる。

ではいよいよ起動。
以下のように設定・保存したプロパティファイルを指定した上で起動する。

> bin\transactor config\sql-transactor.properties
'data-dir' property not set, defaulting to 'data' in current directory
'log-dir' property not set, defaulting to 'log' in current directory
************** This system is running in evaluation mode. For a production license, go to http://datomic.com. ****************
Starting datomic:sql://<DB-NAME>?jdbc:oracle:thin:@//localhost:1521/TEST?user=hoge&password=fuga, you may need to change the user and password parameters to work with your jdbc driver ...
System started datomic:sql://<DB-NAME>?jdbc:oracle:thin:@//localhost:1521/TEST?user=hoge&password=fuga, you may need to change the user and password parameters to work with your jdbc driver

ドキュメントによれば System started って出てれば OK とあるがこれ本当に DB と繋がってるんかね?
まあ次の手順で分かるか。

datomic_kvs を覗いてみる

ここでふと寄り道。テーブルにもう何か入ってるのかなと覗いてみた。

SELECT id, rev, DBMS_LOB.SUBSTR(map,2000,1), DBMS_LOB.SUBSTR(val,2000,1) AS VAL
  FROM datomic_kvs

何やら 1 行入ってた。何なんだろこれ。

transactor に接続する(Connecting to the transactor)

親切な事に起動した transactor にちゃんとつなげるかどうかだけであれば JavaClojure のソ
ースを書かなくても良く Java のシェルなるものが用意されててそれで確認できる。

bin\shell.cmd がそれなのでダブルクリックして早速試す。*1

んー動かん。JDBC ドライバロードできなかったから Class.forName した所まではいいんだがぬるぽが出る。

  • JDBC 他におまじないがある?
    • なさそうなんだよなあ。
  • sql-url で指定してる OracleJDBC 接続文字列の方にもユーザ・パスワード入れてみる?
    • テーブルに何かデータ書かれてたからあんま意味ない気がするけど
  • beanshell での Class.forName() 相当は getClass と言うコマンドがあるって試したがダメ
>bin\shell

Datomic Java Shell
Type Shell.help(); for help.
datomic % Class.forName("oracle.jdbc.driver.OracleDriver");
<class oracle.jdbc.driver.OracleDriver>
datomic % uri = "datomic:sql://test?jdbc:oracle:thin:user/pass@//localhost:1521/TEST";
<datomic:sql://test?jdbc:oracle:thin:user/pass@//localhost:1521/TEST>
datomic % Peer.createDatabase(uri);
// Error: // Uncaught Exception: bsh.TargetError: Method Invocation Peer.createDatabase : at Line: 3 : in file: <unknown file> : Peer .createDatabase ( uri )

Target exception: java.lang.NullPointerException

java.lang.NullPointerException
        at java.io.StringReader.<init>(StringReader.java:50)
        at clojure.lang.RT.readString(RT.java:1736)
        at clojure.core$read_string.invoke(core.clj:3427)
        at datomic.coordination$lookup_transactor_endpoint$fn__1979.invoke(coordination.clj:128)
        at datomic.coordination$lookup_transactor_endpoint.invoke(coordination.clj:125)
        at datomic.peer$send_admin_request$fn__9221.invoke(peer.clj:589)
        at datomic.peer$send_admin_request.invoke(peer.clj:583)
        at datomic.peer$create_database.invoke(peer.clj:603)
        at datomic.peer$create_database.invoke(peer.clj:595)
        at clojure.lang.Var.invoke(Var.java:415)
        at datomic.Peer.createDatabase(Peer.java:84)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at bsh.Reflect.invokeMethod(Reflect.java:146)
        at bsh.Reflect.invokeStaticMethod(Reflect.java:94)
        at bsh.Name.invokeMethod(Name.java:874)
        at bsh.BSHMethodInvocation.eval(BSHMethodInvocation.java:75)
        at bsh.BSHPrimaryExpression.eval(BSHPrimaryExpression.java:102)
        at bsh.BSHPrimaryExpression.eval(BSHPrimaryExpression.java:47)
        at bsh.Interpreter.run(Interpreter.java:479)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:93)
        at clojure.lang.Reflector.invokeNoArgInstanceMember(Reflector.java:298)
        at user$eval29.invoke(shell.clj:50)
        at clojure.lang.Compiler.eval(Compiler.java:6619)
        at clojure.lang.Compiler.load(Compiler.java:7064)
        at clojure.lang.Compiler.loadFile(Compiler.java:7020)
        at clojure.main$load_script.invoke(main.clj:294)
        at clojure.main$script_opt.invoke(main.clj:356)
        at clojure.main$main.doInvoke(main.clj:440)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.lang.Var.invoke(Var.java:415)
        at clojure.lang.AFn.applyToHelper(AFn.java:161)
        at clojure.lang.Var.applyTo(Var.java:532)
        at clojure.main.main(main.java:37)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at jline.ConsoleRunner.main(ConsoleRunner.java:73)
datomic %

*1:どうも datomic の bin 以下のユーティリティ群は展開フォルダ直下をカレントフォルダとして動作する事前提のようで、transactor.cmd など bin 以下に移動しても動かなかった

datomic を試してみる(2) - 導入

何はともあれまずはインストール。
今後ちょくちょくリンクを出すことになるが、datomic 公式は以下。
Datomic - Home

ダウンロード

今回は Pro 版をダウンロードした。*1

以下から最新を落としてくる。
http://downloads.datomic.com/pro.html

どっかに置く

本体は jar で固められた Java のライブラリなのだが、初期設定や簡単な動作確認に便利なコマンドラインツールも含まれているのでダウンロードした zip 展開フォルダ毎どこか適当な場所に置く。

Storage Service を使えるようにする

元ドキュメントだと以下に当たる。
Datomic Development Resources

datomic では Storage と言うある意味汎化されたデータソースを通じてやりとりすると言う仕組み。
SQL DB/DynamoDB/Riak/Couchbase/Infinispan memory cluster/ローカルファイル/オンメモリをバックエンドのDBとする事が可能。ただし Free 版で可能なのはローカルファイルとオンメモリのみ。ちなみに RDBMSJDBC ドライバさえ提供されていればどれでも良いとの事。

以降では上記公式ドキュメントと対応する見出しをなるべく入れるようにする。

評価キー送付依頼(Request an evaluation key)(任意)

RDBMS などを storage として試したい場合、評価キーをもらえれば 30 日間は無償で試せる。依頼先 URL は以下でメールアドレスさえ持っていればすぐもらえる。

上記リンクで reCAPTCHA で表示されるパスワードとメールアドレス入れたら確認メールが来るのでそこにある確認リンクに飛べば確認完了し、評価キーが記載されたメールが来る。

ローカルファイル Storage で試す(Provisioning dev mode)

せっかく評価キーを発行してもらってるので今回は dev mode と言うローカルファイルをストレージに見立てたものを試す。

評価キーインストール(Install your licence key)

公式だとライセンスキーを transactor の設定ファイル項目 license-key に入れるだけというまあその通りではあるんだが、
~/config/sample/dev-transactor-template.properties と言うテンプレートがあるので、これを
~/config/sample/dev-transactor.properties にリネームコピーしたものに以下の様な感じで license-key を入れて使う事にする。

license-key=(もらった評価キー)

もらったメールにある評価キーは 6 行に渡ってるが、改行はバックスラッシュでエスケープされてるのでそのままコピペすれば良い。

transactor 起動

~ 以下でさっき保存した ~\config\dev-transactor.properties を指定して transactor 起動する。

C:\datomic-pro-0.8.4218>bin\transactor config\dev-transactor.properties
'data-dir' property not set, defaulting to 'data' in current directory
'log-dir' property not set, defaulting to 'log' in current directory
************** This system is running in evaluation mode. For a production license, go to http://datomic.com. ****************
Starting datomic:dev://localhost:4334/<DB-NAME>, storing data in: data ...
System started datomic:dev://localhost:4334/<DB-NAME>, storing data in: data

Starting datomic~ となっていれば良いらしい。

transactor 接続確認(Connecting to the transactor)

beanshell と言う何やら Java をスクリプトっぽく扱うツールも含まれてるのでコマンドプロンプトをも一個立ち上げて繋がるか試す。

C:\datomic-pro-0.8.4218>bin\shell
Datomic Java Shell
Type Shell.help(); for help.
datomic % uri = "datomic:dev://localhost:4334/test";
<datomic:dev://localhost:4334/test>
datomic % Peer.createDatabase(uri);
<true>
datomic % Peer.connect(uri);
<{:db-id "test-0c83c230-01f8-4c19-b559-e30a7d80956f", :unsent-updates-queue 0, :
pending-txes 0, :next-t 1000, :basis-t 62, :index-rev 0, :index-root {:rev 0, :k
ey "5268d7e7-9bec-4f7c-9731-a6c542ff88f0"}, :log-root {:rev 1, :key "5268d7e7-0c
60-46dd-b423-6693e2522019"}}>
datomic %

transactor 起動時にも書いてあるんだが、接続情報 datomic:dev://localhost:4334/ の後に DB 名(ここでは test)で test と言う名の datomic DB が作成される。

んで、その作成された DB に対して Peer.connect()する事で実際に繋がるかどうかの確認となる。何かしらの内部的なマップデータらしきものが返されれば良いっぽい。
*2

SQL で言うテーブル・フィールド定義や SELECT に当たるものは次々回(?)で。

*1:この後 Free 版落として peer から transactor に繋ぐ所まで確認してるのだが、当初既存の Oracle DB をストレージにしようと考えていた名残

*2:Free 版の free プロトコル起動での data フォルダ以下を比較した感じ Pro 版での dev と同じローカルファイルと思われる。

datomic を試してみる(1) - datomic って何?

サブタイトルに datomic って何?と付けてるもののまだ試した事はないので紹介・説明に関しては以下の記事に譲る。

一言で言うと、新しいデータベースシステムの一種。

興味を持つかどうかの取っ掛かりとしては一番上の hozumi さんの説明が良いかも、と言うか自分がこれで結構前に興味を持つように。ついでにも一つ興味を持った点としては、このシステムは書込はボトルネックになり得ると言う事。明確にデメリットを説明してくれるシステムと言うのは個人的に好感が持てる。

自分は、上記記事を読んでると RDBMS を相手にしたアプリ作る時に面倒だと思った以下の様な事が datomic を使うと楽になるかも?*1と言う気がしてきたので、実際に楽になるかどうか確認していくと言う視点で書いていけたらと思う。

  • RDBMS データ <-> ホスト言語*2のマッピングってどうしよう
  • DB 接続管理どうしよう
  • 常時あるデータをモニタリングするようなツールを作る時の更新周期やキャッシュするかどうかなどどうしよう
  • SQL ゴリゴリ書いてると WHERE 句だったり CASE 句だったりスカラサブクエリとか部品化できそうなスニペットが結構あるんだけどどうもやり辛い
    • 他には動的にクエリを生成したいと思った事もしばしば

*1:こういうのユースケースって言うんだっけ

*2:ホスト言語 <-> GUI って話もあるんだがここではひとまず置いておく