Java+PGXでグラフクエリを爆速で叩く

#1. 初めに
- プロパティグラフに触れる機会があったので、備忘録として残す気持ちで書きます。
- グラフと聞くと、グラフ理論のようなアカデミックな数学を連想し易いですが、「グラフデータベース」を活用する場合はグラフ理論のような複雑な話はあまり必要ありません。
- ここでは「グラフデータベース」にフォーカスを当ててながら認めます。

#2. グラフとは
グラフは一言で言うと、単なる頂点(Vertex)と辺(edge)の集合です。
グラフはエンティティをノードとして表し、エンティティが世界とどのように関連しているかを関係として表現します。
グラフのデータモデルは以下の3つがあります。

###プロパティグラフ
プロパティグラフの特徴として下記4つあります。

- プロパティグラフにはノードと関係が含まれている。
- ノードには、プロパティ(Key/Value)が含まれている。
- 関係には名前と方向(direction)があり、必ず開始ノードと終了ノードがあります。
- 関係にもプロパティを含めることができます。

例)　簡単なプロパティグラフ

<img width="700" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/f5bbf1d6-e72f-ec0c-c122-ee00f4a41fab.png">


###ハイパーグラフ
ハイパーグラフは関係を任意の数のノードに接続できる汎用的なグラフモデルです。
前述のプロパティグラフでは関係は1つの開始ノードから終了ノードしか持てないが、ハイパーグラフでは任意の数のノードに接続できます。

例)　アリスとボブが車を3台所有していることを表現するハイパーグラフ

<img width="700" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/a712289c-effb-ffac-e12d-b0a8074f94b5.png">


###トリプル
トリプルは「主語- 述語- 目的語」というデータ構造になります。
トリプルはRDF(Resource Description Framework)のメタデータモデルに当たります。
逆に言うと、RDFはリソース(定義されていないこと)をトリプルで表現します。

例) 「Ora Lassilaは、資源(http://www.w3.org/Home/Lassila) の作者である」 という自然言語をトリプルで表現するRDF

- 文の構成

| 主語 | リソース | http://www.w3.org/Home/Lassila |
-----------|-----------| -----------
| 述語 | プロパティ  |  Creator |
| 目的語  | プロパティ値  | "Ora Lassila" |

- RDF/XML表現

```xml
<rdf:RDF>
  <rdf:Description about="http://www.w3.org/Home/Lassila">
    <s:Creator>Ora Lassila</s:Creator>
  </rdf:Description>
</rdf:RDF>
```

※今回はプロパティグラフのお話なので上記ハイパーグラフとトリプルの話は関連しておりません。
#3. 環境(Parallel Graph Analytix)
##PGX
今回はParallel Graph Analytix(通称:PGX)と呼ばれるグラフツールキットを用います。
PGXには、グラフクエリ言語、多様な分析機能やMachine Learningのサポートが含まれています。
下記の絵はPGXの全体概要です。
<img width="700" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/9ad64ea8-0131-d373-cac8-49b1eeb2a71f.png">

##Oracle Cloud Infrastructure
PGXをどのように用いるのか、簡単なクラウドアーキテクチャは下記の通りです。
簡素化のために全てPublic Subnetベースで環境を整えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/b424e802-220d-b1a3-35f2-53aa27933bd0.png)

中身は下記の通りです。
###クライアントサーバ
- oracle-instantclient19.6-basic
- oracle-graph-client-20.3.0
- jdk-11.0.3

###グラフサーバ
- oracle-graph-20.3.0
- jdk-1.8.0
- ※今回は特に使用しておりませんが、Tomcatへデプロイすることも可能です

###DBサーバ
- Autonomous Database(19c)　<- DBCSでも構いません
- OPG_APIS.CREATE_PG()プロシージャでグラフを作成する

```bash

SQL> Execute OPG_APIS.CREATE_PG('Graph',4,8,'USERS');
SQL> select table_name from user_tables;
TABLE_NAME
--------------------
GRAPHGT$
GRAPHIT$
GRAPHSS$
GRAPHVT$
GRAPHGE$

SQL> desc GRAPHVT$
 Name               Null?         Type
 ------------------ ---------- --------------------- 
 VID                NOT NULL   NUMBER
 VL                            NVARCHAR2(3100)
 K                             NVARCHAR2(3100)
 T                             NUMBER(38)
 V                             NVARCHAR2(15000)
 VN                            NUMBER
 VT                            TIMESTAMP(6) WITH TIME ZONE
 SL                            NUMBER
 VTS                           DATE
 VTE                           DATE
 FE                            NVARCHAR2(4000)
```


#4. グラフクエリ(PGQL)を投げるところまで
## RDBデータ
###作成するプロパティグラフ
Googleで検索するとサンプルデータが豊富に出てきますが、それらを使ってもつまらないので、ショボいグラフではありますが自分で作ってみます。素直にRDB側のGRAPHVT＄表とGRAPHGE＄表へInsertします。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577151/b597989c-4cd3-9cec-377d-8d1639ca0c01.png)



###ノード作成
```sql
insert into GRAPHVT$ (VID,VL,T,K,V) values (1,'person',1,'name','Sato');
insert into GRAPHVT$ (VID,VL,T,K,VN) values(1,'person',2,'age',40);
insert into GRAPHVT$ (VID,VL,T,K,V) values(2,'person',1,'name','Suzuki');
insert into GRAPHVT$ (VID,VL,T,K,VN) values(2,'person',2,'age',20);
insert into GRAPHVT$ (VID,VL,T,K,V) values(3,'person',1,'name','Yamamoto');
insert into GRAPHVT$ (VID,VL,T,K,VN) values(3,'person',2,'age',35);
insert into GRAPHVT$ (VID,VL,T,K,V) values(4,'person',1,'name','Tanaka');
insert into GRAPHVT$ (VID,VL,T,K,VN) values(4,'person',2,'age',25);
```

###エッジ作成
```sql
create sequence graph_eid_seq; 
alter sequence graph_eid_seq restart;
insert into GRAPHGE$ (EID,SVID,DVID,EL,K,T,VN) values(graph_eid_seq.nextval,1,2,'knows','weight',3,0.5);
insert into GRAPHGE$ (EID,SVID,DVID,EL,K,T,VN) values(graph_eid_seq.nextval,1,4,'knows','weight',3,0.5);
insert into GRAPHGE$ (EID,SVID,DVID,EL,K,T,VN) values(graph_eid_seq.nextval,4,2,'likes','weight',3,0.8);
insert into GRAPHGE$ (EID,SVID,DVID,EL,K,T,VN) values(graph_eid_seq.nextval,4,3,'knows','weight',3,0.7);
insert into GRAPHGE$ (EID,SVID,DVID,EL,K,T,VN) values(graph_eid_seq.nextval,3,1,'knows','weight',3,0.9);
```
## JShellによる接続
Jshellを用いた場合の接続方法を記します。

```bash

[oracle@cli bin] curl -X POST -H 'Content-Type: application/json' -d '{"username": "***", "password": "***"}' http://10.51.0.2:7007/auth/token
->正しくコマンドを打つと、Access Tokenが返ってきます

[oracle@cli bin] ./oracle-graph-client-20.3.0/bin/opg-jshell --base_url http://10.51.0.2:7007

enter authentication token (press Enter for no token): <-Curlコマンドで取得したTokenをコピペします
For an introduction type: /help intro
Oracle Graph Client Shell 20.3.0
PGX server version: 20.1.1 type: SM
PGX server API version: 3.8.1
PGQL version: 1.3
Variables instance, session, and analyst ready to use.

opg-jshell> GraphConfig cfg = GraphConfigBuilder.forPropertyGraphRdbms()
.setName("Graph")
.addVertexProperty("name",PropertyType.STRING)
.addVertexProperty("age",PropertyType.INTEGER)
.addEdgeProperty("weight",PropertyType.FLOAT)
.setLoadVertexLabels(true)
.setLoadEdgeLabel(true).build();　<-扱うグラフを定義する

opg-jshell> PgxGraph graph = session.readGraphWithProperties(cfg);　<-RDBからOn-Memoryへグラフをロードする
graph ==> PgxGraph[name=Graph,N=4,E=5,created=1596986537591]

opg-jshell> graph.queryPgql("SELECT count(v) FROM Graph MATCH (v)").print(10).close(); <-PGQL(1)
+----------+
| count(v) |
+----------+
| 4        |
+----------+

opg-jshell> 
opg-jshell> graph.queryPgql("SELECT id(n), label(n),n.name as name1,n.age as age1,label(e), e.weight, id(m),label(m),m.name as name2,m.age as age2 FROM MATCH (n) -[e]-> (m)").print(10).close();
<- PGQL(2)
+---------------------------------------------------------------------------------------------+
| id(n) | label(n) | name1    | age1 | label(e) | weight | id(m) | label(m) | name2    | age2 |
+---------------------------------------------------------------------------------------------+
| 3     | person   | Yamamoto | 35   | knows    | 0.9    | 1     | person   | Sato     | 40   |
| 4     | person   | Tanaka   | 25   | knows    | 0.7    | 3     | person   | Yamamoto | 35   |
| 4     | person   | Tanaka   | 25   | likes    | 0.8    | 2     | person   | Suzuki   | 20   |
| 1     | person   | Sato     | 40   | knows    | 0.5    | 4     | person   | Tanaka   | 25   |
| 1     | person   | Sato     | 40   | knows    | 0.5    | 2     | person   | Suzuki   | 20   |
+---------------------------------------------------------------------------------------------+
opg-jshell>
```

## Javaによる接続
Javaでの接続を記します。JshellでしかPGXを触れない場合はこちらは必要ありません。
ただ、Javaの方がInteractiveに実行せずに済むのでラクです。

```java:TokenConnect.java
import oracle.pgx.api.*;
import oracle.pgx.config.*;
import oracle.pg.rdbms.*;
import oracle.pgx.common.types.*;
import java.util.function.Supplier;

public class TokenConnect{
    public static void main(Srting[] args) throws Exception{
        /*引数にはURLとCurlで取得したTokenを指定する仕様にしてあります*/
        String baseUrl = args[0];
        String token = args[1];
        ServerInstance instance = Pgx.setInstance(baseUrl,token);
        try (PgxSession session = instance.createSession("my-session")){
            Supplier<GraphConfig> cfg = () ->{return GraphConfigBuilder.forPropertyGraphRdbms()
            .forPropertyGraphRdbms()
            .setName("Graph")
            .addVertexProperty("name",PropertyType.STRING)
            .addVertexProperty("age",PropertyType.INTEGER)
            .addEdgeProperty("weight",PropertyType.FLOAT)
            .setLoadVertexLabels(true)
            .setLoadEdgeLabel(true)
            .build();
        
            PgxGraph graph = session.readGraphWithProperties(cfg.get());
            System.out.println("N = " + graph.getNumVertices()+ " <-> E = " +    graph.getNumEdges());
            graph.queryPgql("SELECT id(n), label(n),n.name as name1,n.age as age1,label(e), e.weight, id(m),label(m),m.name as name2,m.age as age2 FROM MATCH (n) -[e]-> (m)").print(10).close();
        }
    }
}
```

```bash

[oracle@cli oracle-graph-client-20.3.0] javac -cp 'lib/*' TokenConnect.java
warning: Supported source version 'RELEASE_8' from annotation processor 'org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDslProcessor' less than -source '11'
1 warning

[oracle@cli oracle-graph-client-20.3.0] java -cp '.:conf:lib/*' TokenConnect *baseUrl *Token
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.google.inject.internal.cglib.core.$ReflectUtils$1 (file:/home/opc/oracle-graph-client-20.3.0/lib/guice-4.2.2.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of com.google.inject.internal.cglib.core.$ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

N = 4　<-> E = 5
+---------------------------------------------------------------------------------------------+
| id(n) | label(n) | name1    | age1 | label(e) | weight | id(m) | label(m) | name2    | age2 |
+---------------------------------------------------------------------------------------------+
| 3     | person   | Yamamoto | 35   | knows    | 0.9    | 1     | person   | Sato     | 40   |
| 4     | person   | Tanaka   | 25   | knows    | 0.7    | 3     | person   | Yamamoto | 35   |
| 4     | person   | Tanaka   | 25   | likes    | 0.8    | 2     | person   | Suzuki   | 20   |
| 1     | person   | Sato     | 40   | knows    | 0.5    | 4     | person   | Tanaka   | 25   |
| 1     | person   | Sato     | 40   | knows    | 0.5    | 2     | person   | Suzuki   | 20   |
+---------------------------------------------------------------------------------------------+
[oracle@cli oracle-graph-client-20.3.0]
```
しっかりとPGQLクエリの結果が返ってきました。

#5. まとめ
- PGXを用いて自作グラフからクエリ検索しました。
- サーバをそれぞれ3層に分けてリモートで接続できることを確認できました。
- JshellとJavaの両方で使い分けできることも確認しました。

# 参考
- インストール手順の参考 ->
[PGXのDocument](https://docs.oracle.com/cd/E56133_01/latest/installation.html)