Gremlinクエリ言語を使う為の設定とGremlinクエリ言語の使い方をまとめた

# 概要
グラフデータを扱う機会があり、`Gremlin`クエリ言語にて、グラフDBの操作を行なっております。私の主観ですが、使用頻度が高いと思われる`Gremlin`クエリ言語をまとめます。

# 環境
- グラフDBは、`Amazon Nputune`を使用
- `Gremlin`クエリは、`Python`から`Apache TinkerPop`の`Gremlin-Python`ライブラリをインストールして操作
- `Jupyter notebook`を使用して、グラフデータの操作、可視化を行なっている
- `Amazon SageMaker`を使用して、`Jupyter notebook`の環境を立ち上げた

# 設定の確認


## ステータスの確認
```py
%status

# 出力内容
# 下記が表示されていれば、正常に動作している
'status': 'healthy'
```

## 接続しているDBの情報の確認

:::note warn
私の環境では、Amazon Nputuneに接続している為、Nputuneの接続情報が記載されております
:::
```py
%%bash
cat ~/graph_notebook_config.json

# 表示内容
{
  "host": "db-neptune.cluster-************.リージョン.neptune.amazonaws.com",
  "neptune_service": ************,
  "port": port番号,
  "proxy_host": "",
  "proxy_port": port番号,
  "auth_mode": "DEFAULT",
  "load_from_s3_arn": "",
  "ssl": true,
  "ssl_verify": true,
  "aws_region": リージョン,
  "sparql": {
    "path": "sparql"
  },
  "gremlin": {
    "traversal_source": "g",
    "username": "",
    "password": "",
    "message_serializer": ************
  },
  "neo4j": {
    "username": ******,
    "password": ******,
    "auth": ****,
    "database": ****
  }
}

```

# グラフDBを操作する為の設定　
:::note warn
注意事項
- `Python`で記述する必要がある
:::
## ライブラリのインポート
- `Gremlin Server`に接続する為の必要なライブラリをインポート
```py
from gremlin_python.structure.graph import Graph
from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection
```
**Gremlin Server** : グラフDBをネットワーク越しに操作する為のサーバ

## コネクションの設定
- `DriverRemoteConnection`を使用して、`Gremlin Server`への接続を行う
- `DriverRemoteConnection` の第一引数に、エンドポイントとポート番号、第二引数にグラフのエイリアス（<font color="Red">**g**</font>）を指定する

```py
connection = DriverRemoteConnection(
  'wss://db-neptune.cluster-************.リージョン.neptune.amazonaws.com:8182/gremlin',
  'g'
)
```
**第二引数の補足**
`Gremlin Server`では、デフォルトのグラフエイリアスとして<font color="Red">**g**</font>を使用しする。これにより、クライアントが<font color="Red">**g**</font>を指定する事で、デフォルトのグラフDBにアクセス出来る

グラフエイリアス : `Gremlin Server`内で特定のグラフDBを参照する為の名前（別名）
デフォルトグラフDB : `Gremlin Server`が起動時に自動で読み込むグラフDB



## トラバーサル(探索)ソースの設定
- トラバーサルソースを設定する事で、リモートの`Gremlin Server`上のグラフDBに対してクエリを実行する事が可能
- 上記の設置により、リモートのグラフDBを検索・操作が可能となる
```py
g = Graph().traversal().withRemote(connection)
```
トラバーサル : `Gremlin`クエリを使ってグラフDB上で行う全ての操作（ノードやエッジの挿入、削除、更新、データ取得）の事を示す

## 接続を切る
- 使用後、接続をクローズしてリソースを解放する
```
connection.close()
```

## 書き方
```py
from gremlin_python.structure.graph import Graph
from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection

# 構文
g = Graph().traversal().withRemote(
  DriverRemoteConnection('wss://DBのエンドポイントを記述:port番号/gremlin','g')
)

# 入力例 
connection = DriverRemoteConnection(
  'wss://db-neptune.cluster-************.リージョン.neptune.amazonaws.com:port/gremlin','g'
)
g = Graph().traversal().withRemote(connection)
```

# `gremlin`クエリのみの書き方
- 冒頭に下記を定義する。それ以降は、`Gremlin`クエリでグラフDBの操作が可能となる
- `%%gremlin`が冒頭に記載されている場合は、その記述は、`Gremlin`クエリのみが記述されていると判断して良い
```py
%%gremlin
```


# S3に接続する方法
:::note warn
注意事項
- S3に接続する為には、IAMロールの修正が必要になる場合がある
:::

## ライブラリのインポート
```py
import boto3
```

## S3に接続する
```py
s3 = boto3.client('s3')
obj = s3.get_object(
  Bucket='バケット名',
  Key='ファイルパス'
)
```


# pandasのread_csvの使用方法
- データ解析を容易に行う為のデータ解析ライブラリ
- グラフDBを使用する際は、使う頻度が多いと思われる



## ライブラリのインポート
```py
import pandas as pd
```

## CSVの取得方法
```py
# ローカル環境にあるcsvを取得する場合は、下記の方法
filepath = 'test0.csv'
print(Path(filepath).read_text())
#0.0,1.1,2.2
#3.3,4.4,5.5
#6.6,7.7,8.8

df = pd.read_csv(filepath)
print(df)
#0.0,1.1,2.2
#3.3,4.4,5.5
#6.6,7.7,8.8

# s3からCSVを取得する場合は、read_csvの引数に、obj['Body']を入力する
# objの[]は、様々なデータと取得する事が可能。詳細は、下記のget_objectの詳細を参照
data = pd.read_csv(obj['Body'])
```
- [[pandas 公式] What kind of data does pandas handle?](https://pandas.pydata.org/pandas-docs/stable/getting_started/intro_tutorials/01_table_oriented.html)
- [pandasでCSVファイルの書き込み・追記（to_csv）](https://note.nkmk.me/python-pandas-to-csv/)
- [[AWS 公式]get_objectの詳細](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/get_object.html)


# nest_asyncio
- `Jupyter Notebook`では、セルごとにコードを実行し、その結果をインタラクティブに表示する為、バックグラウンド(裏側)で処理が常に動いている
-  `Jupyter Notebook`環境では、`asyncio`によって、イベントループが実行されている為、新たなイベントループ(今回の場合であれば、`Gremlin`クエリーを叩く)を開始しようとするとエラーが発生する
-  このエラーを発生しない様にする 為、`nest_asyncio`を使用して、新しい非同期タスクを実行出来る様に様にする

イベントループ : 非同期タスクを管理、スケジュール、実行する為の仕組み
インタラクティブ : ユーザーとシステムが対話的にやり取りを行う事を意味する

## ライブラリのインポート
```py
import nest_asyncio
```

Gremlinクエリーを叩く前に実行しておく
```py
nest_asyncio.apply()
```
- [nest-asyncio](https://pypi.org/project/nest-asyncio/)


# データの追加
:::note warn
データ挿入の注意事項
ノードとエッジのデータ挿入を`Python`で記述する場合、下記の記述をデータ挿入の前に書く必要がある。


```py
from gremlin_python.structure.graph import Graph
from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection

connection = DriverRemoteConnection(
  'wss://DBのエンドポイントを記述:port番号/gremlin','g'
)
g = Graph().traversal().withRemote(connection)

#
# 以下省略
```
詳細は下記
[PythonでグラフDBを操作する為の設定](https://qiita.com/drafts/01eab272174a53e54ba5/edit#%E3%82%B0%E3%83%A9%E3%83%95db%E3%82%92%E6%93%8D%E4%BD%9C%E3%81%99%E3%82%8B%E7%82%BA%E3%81%AE%E8%A8%AD%E5%AE%9A)
:::
## ノードとエッジの追加

### ノードで使うメソッド
`addV()` : ノードにラベルを付与する(名前は任意で決めて良い)

### エッジで使うメソッド
`addE()` : エッジにラベルを付与する(名前は任意で決めて良い)
`from()` : エッジの始点を定義する (`Python`で書く場合は、`from_()`で記述する)
`to()` : エッジの終点を定義する


### ノードとエッジ、両者で使うメソッド
`property` : 
- `key`と`value`の形式で値を付与する
- `property`は複数付与する事が可能

`next()` : 
- 新しい頂点やエッジを追加した後に、その結果を<font color="Red">参照する</font>場合に使用する
- `Python`にて、グラフDBの操作を行う場合、変数に保持して使用する機会が多い為、`next()`を使う (<font color="Red">必須ではない</font>)

`iterate()` : 
- 新しい頂点やエッジを追加した後に、その結果を<font color="Red">参照しない</font>場合に使用する

`as` :  (`Python`で書く場合は、`as_()`で記述する)
- 新しい頂点やエッジを追加した後に、その結果に対してラベルを付与する
- Gremlinのトラバーサル内で、ラベルを使ってノードやエッジを参照する為、連続した操作がシンプルに記述できる 
- Gremlinクエリのみで記述する場合に使う

(トラバーサル : `Gremlin`クエリを使ってグラフDB上で行う全ての操作（ノードやエッジの挿入、削除、更新、データ取得）の事を示す)

### pythonでの記述(参考例)
```py
# ノードの追加
node1 = g.addV('Node').property('node_id', 1).property('name', 'test1').next()
node2 = g.addV('Node').property('node_id', 2).property('name', 'test2').next()

# エッジの追加
g.addE('edge').from_(node1).to(node2).iterate()

# プロパティー有りのエッジを追加
g.addE('edge').from_(node1).to(node2).property('edge_id',1).property('edge_name', 'edge_test1').iterate()
```

### Gremlinクエリのみでの記述(参考例)
```py
# ノードの追加
g.addV('Node').property('node_id', 1).property('node_name', 'node1').as('node1').
  addV('Node').property('node_id', 2).property('node_name', 'node2').as('node2').

# プロパティー無しのエッジの追加
  addE('edge').from('node1').to('node2').iterate()

# プロパティー有りのエッジを追加
  addE('edge').from('node1').to('node2').
  property('edge_id',1).property('edge_name', 'edge_test1').
  iterate()
```

### pythonで記述する場合の省略形の書き方
- Pythonでは、(.)で書くとメソッドチェーン（一連のメソッド呼び出し）になり、エラーとなる。その為、各行の末尾に( \ )を置く事で、処理の継続を示す事が出来る
- コードが複数行にわたって続いても、一つの連続した操作として扱われる

```py
g.addV('Node1').property('node_name', 'node1').property('id', 1).as_('node1').\
  addV('Node2').property('node_name', 'node2').property('id', 2).as_('node2').\
  addV('Node3').property('node_name', 'node3').property('id', 3).as_('node3')
```


## ノードとエッジの消去
- `drop()`の後に`iterate()`を追加する事で、全てのノードとエッジの削除が行われる
- ノードが削除されると、それに接続されているエッジも自動的に削除される

### ノードの消去
```py
g.V().drop().iterate()
```

### エッジの消去
```py
g.E().drop().iterate()
```

### 補足
- `drop()`のみでは、ノードとエッジの削除は実行されない
- 削除対象(ノードとエッジ)がキューに追加された状態で保持される
```py
g.V().drop()
g.E().drop()
```

# グラフデータの出力、検索、フィルタリング
- Gremlinクエリを使用してデータを追加
- 下記に登録したデータを使用して説明を行う
```py
%%gremlin

# データの初期化
g.V().drop().iterate()

g.
  addV('Node').property('node_id', 1).property('node_name', 'node1').as('node1').
  addV('Node').property('node_id', 2).property('node_name', 'node2').as('node2').
  addV('Node').property('node_id', 3).property('node_name', 'node3').as('node3').
  addE('Edge').from('node1').to('node2').property('edge_id', 1).property('edge_name', 'edge1').
  addE('Edge').from('node1').to('node3').property('edge_id', 2).property('edge_name', 'edge2').
  iterate()
```

## 全てのノードを出力
`g.V()` : グラフ内のすべての頂点をオブジェクト形式で表示する
```py
%%gremlin
g.V()

# 出力結果
v[8ac7fed5-f443-fd00-12cb-9793383bb568]
v[0ac7fed5-f44d-4b71-fc4d-c6d120650fb7]
v[aac7fed5-f451-550b-0177-f8fe98791bef]
```

`g.V().valueMap()` : 各ノードのプロパティの値を出力する。プロパティは、`key`と`value`のペアで出力される
```py
%%gremlin
g.V().valueMap()

# 出力結果
{'node_name': ['node1'], 'node_id': [1]}
{'node_name': ['node2'], 'node_id': [2]}
{'node_name': ['node3'], 'node_id': [3]}
```
<font color="Red">※ プロパティーを定義していないとオブジェクトが表示される</font>


## 全てのエッジを出力
`g.E()` : グラフ内のすべてのエッジをオブジェクト形式で表示する
```py
g.E()

# 出力結果
e[28c7fed5-f456-ecd8-a24e-e445b8e89770][8ac7fed5-f443-fd00-12cb-9793383bb568-edge->0ac7fed5-f44d-4b71-fc4d-c6d120650fb7]
e[68c7fed5-f45f-8bb0-49ca-d77a8bc12c6d][8ac7fed5-f443-fd00-12cb-9793383bb568-edge->aac7fed5-f451-550b-0177-f8fe98791bef]

```

`g.E().valueMap()` : 各エッジのプロパティの値を出力する。プロパティは`key`と`value`のペアで出力される
```py
g.E().valueMap()

# 出力結果
{'edge_name': 'edge1', 'edge_id': 1}
{'edge_name': 'edge2', 'edge_id': 2}
```
<font color="Red">※ プロパティーを定義していないとオブジェクトが表示される</font>

## 指定したのノード or エッジを出力する
`g.V().has` : 特定のラベルを持つ頂点 or エッジを検索する際に使用する
`g.V().hasLabel` : 特定のプロパティを持つ頂点 or エッジを検索する際に使用する
 (`key`のみの検索 or `key`と`value`の両方を記述した検索が可能 )


```py
# 構文(ノード)
g.V().hasLabel('lavel')
g.V().has('property')
# or
g.V().has('key', 'value')

# 構文(エッジ)
g.E().hasLabel('lavel')
g.E().has('property')
# or
g.V().has('key', 'value')


入力例
# ノード
g.V().has('Node')
g.V().hasLabel('node_name', 'node1')

# エッジ
g.E().has('Edge')
g.E().hasLabel('edge_name', 'edge1')

```
- 上記の記述は、 オブジェクト形式の値が出力される
- `valueMap()`を末尾に追加する事で、プロパティーの出力出来る

# エッジに紐付いている始点のノードを出力する
- `inV()`単体で使用する場合は、`g.E()`のみに使用可能
- `g.V()`に使用する場合は、`outE()`と組み合わせる事で使用可能となる
```py
# グラフ内のすべてのエッジを見て、そのエッジが向かう先のノード（ターゲットノード）を取得する
g.E().inV()

# グラフの中のすべてのノードから出発して、それぞれのノードから出ているエッジの先にあるノードを取得する
g.V(),outE().inV()


# g.V()：グラフ内のすべてのノード（頂点）を選択する
# outE()：選択された各ノードから出ているすべてのエッジを選択する
# inV()：選択されたエッジが向かう先のノード（ターゲットノード）を取得する
```

# その他
## path() 
- グラフDBの可視化
- クエリがどの様にグラフを通過したかを確認する際に使用する
```py
g.V().path()
```
- `g.E()`には、使用不可
- `Jupyter Notebook`で、グラフを可視化する場合は、追加のライブラリ（例えば NetworkX や matplotlib）が必要
- `Amazon SageMaker`を使用して、`Jupyter notebook`を起動した場合、グラフの可視化ツールはインストールされている為、何か新たにインストールをする必要はない

## values()
- ノード or エッジのプロパティを表示
- 指定した、ノード or エッジのプロパティを表示する事も可能
```py
g.V().values()
g.E().values()

# 指定した場合
g.V().values('test')
g.E().values('test')
```

## by()
- 特定のプロパティを抽出したり、グラフDBのデータを必要な形式に整形する事が可能
- `by()`単体で使用する事は不可能 (例 : `g.V().by('test')`)
- `group()`, `order()`, `select()`等のメソッドと組み合わせて使う
```py
by()
```

## toList()
- グラフDBの結果をリスト形式で表示する
```py
toList()
```


# 参考資料
- [[公式] Connecting Gremlin](https://tinkerpop.apache.org/docs/current/reference/#connecting-gremlin-server)
- [[公式] Getting Started](https://tinkerpop.apache.org/docs/current/tutorials/getting-started/)

# まとめ
今回は、`Gremlin`クエリの基本的な使い方について理解しました。また、分かり次第、追加して記事を更新して行きたいと思います。