https://tinkerpop.apache.org/download.html からZipをDLにして展開、適当な場所に配置する。
bin/gremlin.bat を一般ユーザーで実行する。
\,,,/
(o o)
-----oOOo-(3)-oOOo-----
plugin activated: tinkerpop.server
plugin activated: tinkerpop.utilities
plugin activated: tinkerpop.tinkergraph
:exit で終了できる。
空のDBを開く場合。
graph = TinkerGraph.open()
g = graph.traversal()
内蔵のサンプルデータ「Modern」を読むならこうする。他にClassicもあるらしい
graph = TinkerFactory.createModern()
g = graph.traversal()
g.V().values("name")
データを追加してみる
// 1. 太郎(person)を追加
taro = g.addV('person').property('name', '太郎').next()
// 2. 花子(person)を追加(友達関係のターゲットとして必要)
hanako = g.addV('person').property('name', '花子').next()
// 3. 太郎から花子へ「friends(友達)」のエッジを追加
g.V(taro).addE('friends').to(hanako).iterate()
// 太郎の友達の名前を検索する
g.V().has('name', '太郎').out('friends').values('name')
==> 花子と出ればOK
// 既存頂点に新しいプロパティを追加する(年齢)
// 太郎に年齢(age)と出身地(city)を追加
g.V().has('person', 'name', '太郎').
property('age', 30).
property('city', '東京').iterate()
// 花子に年齢(age)を追加
g.V().has('person', 'name', '花子').
property('age', 28).iterate()
// 太郎のプロパティをすべて表示
g.V().has('name', '太郎').valueMap()
==> [name:[太郎], age:[30], city:[東京]]
// 30歳以上の人の名前を検索
g.V().has('age', P.gte(30)).values('name')
==> 太郎
// 頂点の追加と複数のプロパティ登録を同時に行う
g.addV('person').property('name', '次郎').property('age', 25).next()
next()は、命令を実行し、処理結果の1行目を取得して次の処理に渡す(または画面に表示する)意味。 典型的には、変数に格納して次に進むときに使う。
iterate()は、命令を実行し、画面に結果を表示せずに終端させる意味。
この例では、personがラベル、nameやageがプロパティだが、なぜそうするのか?
判断基準の一例は、ラベル=一度決めたら絶対に変わらない値。索引(インデックス)に必ずなるため、 「personラベルの頂点だけの集合」など「検索において、まず最初に仕分けることになる項目」はラベルにするといい。
また、その項目の値ごとに全く違う種類のプロパティの組を持つ場合 (例、personは年齢や居住地を持つ、productは価格や発売年を持つ、等)、その項目はラベルにするといい。
プロパティ=途中で変わる可能性がある値。年齢などがそう。 ラベルを更新する処理は重いため、変わる可能性がある値はラベルにしないのが鉄則らしい。
エッジについても同じ。友達である(friends)という特質は一度決めたら変わらないのでラベルにするが、 いつの友達か(student,mamatomo)やどのくらい深い関係か(rating)などは変わるのでプロパティにするとよい。
TinkerGraphではラベルは1つの頂点に1つしか付けられないが、Neo4Jなどでは複数付けられる。 つまりpersonでありemployeeでもある頂点を作れる。
通常は次のようにすれば、太郎の年齢を30⇒31歳に変更できる。
// 太郎の年齢を30から31に更新する
g.V().has('person', 'name', '太郎').property('age', 31).iterate()
g.V().has('name', '太郎').valueMap()
このとき、同名のプロパティで複数の値を取りうる仕様のDBの場合は、 Cardinarity.Singleを引数に追加する必要があるかも。
g.V().has('person', 'name', '太郎').properties('city').drop().iterate()
g.V().has('person', 'name', '太郎').property('city', '横浜').iterate()
propertyは動詞。プロパティ値の設定や更新に使う。末尾がnext()やiterate()になることが多く、 引数は必ず2つ(キー、値)
propertiesは名詞。プロパティ値の取得に使う。直後にdrop()やvalue()が来ることが多く、 引数は必ず1つ(キーのみ)
なお、次の2行は同じ意味。propertiesで取得した値をただ見るだけなら、valuesを使う方が簡潔。
g.V().properties('age').value()
g.V().values('age')
// 太郎から出ている「friends」エッジを全て削除
g.V().has('person', 'name', '太郎').outE('friends').drop().iterate()
// 太郎から出ているfriendsエッジの内、花子とのfriendsエッジのみを削除
// 方法1
g.V().has('person', 'name', '太郎').
outE('friends').as('e').
inV().has('name', '花子').
select('e').drop().iterate()
// 方法2。こちらの方が若干簡潔
g.V().has('person', 'name', '太郎').
outE('friends').
where(inV().has('name', '花子')).
drop().iterate()
// 花子の頂点を削除する(花子に繋がっていたエッジも消えます)
g.V().has('person', 'name', '花子').drop().iterate()
// 30歳以上の人を検索 (Greater Than or Equal)
g.V().hasLabel('person').has('age', P.gte(30)).values('name')
// 20歳超、35歳未満の人を検索 (Between)
g.V().hasLabel('person').has('age', P.inside(20, 35)).values('name')
// 名前に「郎」が含まれる人を検索
g.V().hasLabel('person').has('name', TextP.containing('郎')).values('name')
// 名前の先頭が「花」で始まる人を検索
g.V().hasLabel('person').has('name', TextP.startingWith('花')).values('name')
// 「30歳以上」かつ「東京在住」の人を検索 (AND)
g.V().hasLabel('person').has('age', P.gte(30)).has('city', '東京').values('name')
// 「東京在住」または「横浜在住」の人を検索 (OR)
g.V().hasLabel('person').or(has('city', '東京'), has('city', '横浜')).values('name')
// 誰かしら「friends(友達)」のエッジを持っている人だけを検索
g.V().hasLabel('person').where(out('friends')).values('name')
// 誰も「friends」のエッジを持っていない(友達がいない)人を検索
g.V().hasLabel('person').not(out('friends')).values('name')
// まず太郎⇒花子⇒次郎の友達関係を構築
jiro = g.V().has('name', '次郎').next()
g.V(hanako).addE('friends').to(jiro).iterate()
// 各自の友達の名前を検索する
g.V().has('name', '太郎').out('friends').values('name')
g.V().has('name', '花子').out('friends').values('name')
// 太郎の「友達の友達」の名前を取得する
g.V().has('person', 'name', '太郎').
out('friends'). // 1ステップ目:花子へ
out('friends'). // 2ステップ目:次郎へ
values('name')
==> 次郎
// 自分自身(太郎)を除外して、友達の友達を探す
g.V().has('person', 'name', '太郎').as('me').
out('friends').
out('friends').
where(P.neq('me')).
values('name')
// 太郎から「friends」の関係をちょうど2回繰り返して到達する人を検索
g.V().has('person', 'name', '太郎').
repeat(out('friends')).times(2).
values('name')
// 太郎から友達の友達へ至るルートをすべて表示する
g.V().has('person', 'name', '太郎').
out('friends').
out('friends').
path().by('name')
==> [太郎, 花子, 次郎]
// 年齢が低い順(昇順)に名前を並び替える(デフォルトは昇順)
g.V().hasLabel('person').order().by('age', Order.asc).values('name')
// 年齢が高い順(降順)に名前を並び替える
g.V().hasLabel('person').order().by('age', Order.desc).values('name')
// 全データの中から、最初の3件だけを取得する
g.V().hasLabel('person').values('name').limit(3)
// 年齢が高い人、上位3人の名前と年齢を取得する
g.V().hasLabel('person').
order().by('age', Order.desc).
limit(3).
valueMap('name', 'age')
// 1. GraphML形式で保存(最も汎用的、Gephiなどの外部ツールで開く際におすすめ)
g.io('my-graph.xml').write().iterate()
// 2. GraphSON形式で保存(中身がテキストのJSON形式なので、人間が読みやすい)
g.io('my-graph.json').write().iterate()
:exit
ファイルはGremlin Consoleのホーム(binの一つ上)に作られる。
// 新しい空のグラフを用意する
graph = TinkerGraph.open()
g = graph.traversal()
// 以前保存したファイルを読み込む
g.io('my-graph.xml').read().iterate()
// 読み込まれたか確認
g.V().valueMap()