5 プロパティ・グラフ問合せ言語(PGQL)

PGQLは、頂点からなり、エッジにより別の頂点に接続され、それぞれが関連付けられたキー値ペア(プロパティ)を持つことができる、プロパティ・グラフ・データ構造のためのSQLライクな問合せ言語です。

この言語はグラフ・パターン一致の概念に基づき、データ・グラフの頂点およびエッジに対し一致するパターンを指定することができます。

プロパティ・グラフ・サポートでは、Java APIを介してプロパティ・グラフ問合せ言語(PGQL)の問合せを実行するための2つの方法が提供されています。

PGQLの詳細は、https://pgql-lang.orgを参照してください。

5.1 PGQLを使用したプロパティ・グラフの作成

CREATE PROPERTY GRAPHは、データベース表からグラフを作成するPGQL DDL文です。グラフはプロパティ・グラフ・スキーマに格納されます。

CREATE PROPERTY GRAPH文は、グラフに付ける名前で始まり、その後に頂点表とエッジ表のセットが続きます。グラフには、頂点表またはエッジ表がない(空のグラフ)か、頂点表がありエッジ表はない(頂点のみでエッジなしのグラフ)か、頂点表とエッジ表の両方がある(頂点とエッジのあるグラフ)場合があります。ただし、エッジ表のみで頂点表のないグラフは指定できません。

次のケースについて検討します。

  • PERSONSは、ID列、NAME列およびACCOUNT_NUMBER列がある表です。この表には、アカウントを持つすべての人の行が追加されます。
  • TRANSACTIONSは、FROM_ACCOUNT列、TO_ACCOUNT列、DATE列、AMOUNT列がある表です。FROM_ACCOUNTからTO_ACCOUNTに送金されるたびに、データベース内のこの表に行が追加されます。

表からグラフへの簡単なマッピングは次のようになります。マップされたグラフの概念は、頂点、エッジ、ラベル、プロパティです。

  • 頂点表: データ・エンティティを含む表は頂点表です。
    • 頂点表の各行は頂点です。
    • 頂点表の列は、頂点のプロパティです。
    • 頂点表の名前は、この頂点セットのデフォルト・ラベルです。あるいは、CREATE PROPERTY GRAPH文の一部としてラベル名を指定できます。
  • エッジ表: エッジ表は、2つの頂点表をリンクする任意の表、またはソース・エンティティからターゲット・エンティティへのアクションを示すデータを含む表です。たとえば、FROM_ACCOUNTからTO_ACCOUNTへの送金は自然なエッジです。
    • 外部キー関係により、データ内で関連しているリンクを知ることができます。CREATE PROPERTY GRAPHは、デフォルトで外部キー関係を使用してエッジを識別します。
    • エッジ表のプロパティの一部は、エッジのプロパティにできます。たとえば、FROM_ACCOUNTからTO_ACCOUNTへのエッジには、DATEおよびAMOUNTプロパティを指定できます。
    • エッジ表の名前は、このエッジ・セットのデフォルト・ラベルです。あるいは、CREATE PROPERTY GRAPH文の一部としてラベル名を指定できます。
  • キー:
    • 頂点表のキー: 頂点表のキーは、グラフ内の一意の頂点を識別します。キーはCREATE PROPERTY GRAPH文で指定できます。指定しない場合、デフォルトで表の主キーになります。表に重複した行がある場合は、CREATE PROPERTY GRAPH文によってエラーが返されます。
    • エッジ表のキー: エッジ表のキーは、グラフ内のエッジを一意に識別します。ソースと宛先の頂点を指定すると、KEY句はソースと宛先の頂点を一意に識別します。

    次に、PERSONS表とTRANSACTIONS表のCREATE PROPERTY GRAPH文の例を示します。

    CREATE PROPERTY GRAPH bank_transfers
         VERTEX TABLES (persons KEY(account_number))
         EDGE TABLES(
                      transactions KEY (from_acct, to_acct, date, amount)
                      SOURCE KEY (from_account) REFERENCES persons
                      DESTINATION KEY (to_account) REFERENCES persons
                      PROPERTIES (date, amount)
           )
    
  • 表の別名: 頂点表とエッジ表には一意の名前が付いている必要があります。同じリレーショナル表から複数の頂点表を識別したり、同じリレーショナル表から複数のエッジ表を識別する必要がある場合は、別名を使用する必要があります。たとえば、次の例に示すように、1つの表PERSONSから2つの頂点表PERSONSとPERSONS_IDを作成できます。
    CREATE PROPERTY GRAPH bank_transfers
         VERTEX TABLES (persons KEY(account_number)
                        persons_id AS persons KEY(id))
    
  • REFERENCES句: エッジのソースおよび宛先の頂点を、対応する頂点表に接続します。

詳細は、https://pgql-lang.org/spec/latest/#creating-a-property-graphを参照してください。

5.2 PGQLを使用したプロパティ・グラフ・ビューの作成

リレーショナル・データベース表にプロパティ・グラフ・ビューを作成できます。

プロパティ・グラフ・ビューを作成するには、PGQLのCREATE PROPERTY GRAPH文を使用します。

プロパティ・グラフ・ビューの作成に使用するCREATE PROPERTY GRAPH文の例は、例2-1を参照してください。

oracle.pg.rdbms.pgqlパッケージのJava APIは、PGQL問合せの実行をサポートしますが、いくつかの例外があります。

SQLclを使用している場合、プロパティ・グラフ・ビューの作成はサポートされません。ただし、作成したプロパティ・グラフ・ビューは、SQLclのPGQL SELECT文を使用して問い合せることができます。Python APIまたはグラフ・ビジュアライゼーション・ツールを使用している場合、プロパティ・グラフ・ビューの作成と問合せは両方ともサポートされません。

また、次のPGQL SELECT機能もサポートされません。

  • 再帰的問合せ
  • 副問合せ
  • in_degree関数とout_degree関数
  • バインド変数

5.3 PGQLによるパターン一致

パターン一致は、MATCH句で1つ以上のパス・パターンを指定することで行われます。単一パス・パターンは頂点およびエッジの線形パスと一致しますが、より複雑なパターンは、複数のパス・パターンをカンマで区切って組み合せることによって一致させることができます。(対応するSQL文に類似した)値式はWHERE句で指定され、通常、頂点およびエッジのプロパティに制約を指定することによって一致をフィルタ処理で除外できます

たとえば、コンピュータ・ネットワーク上のTCP/IP接続のグラフを想定し、誰かが1つのマシンにログインし、そこから別のマシンへ、そこからさらに別のマシンへという場合を検出するとします。次のようなパターンを問合せます。

SELECT id(host1) AS id1, id(host2) AS id2, id(host3) AS id3         /* choose what to return */
FROM MATCH
    (host1) -[connection1]-> (host2) -[connection2]-> (host3)       /* single linear path pattern to match */
WHERE
    connection1.toPort = 22 AND connection1.opened = true AND
    connection2.toPort = 22 AND connection2.opened = true AND
    connection1.bytes > 300 AND                                     /* meaningful amount of data was exchanged */
    connection2.bytes > 300 AND
    connection1.start < connection2.start AND                       /* second connection within time-frame of first */
    connection2.start + connection2.duration < connection1.start + connection1.duration
GROUP BY id1, id2, id3                                              /* aggregate multiple matching connections */ 

パターン一致のその他の例については、PGQL仕様の関連するセクションを参照してください。

5.4 エッジ・パターンはPGQLによって方向を持つ

エッジ・パターンは、グラフのエッジと同様に方向を持ちます。そのため、(a) <-[]- (b)は、bにはaに向けられたエッジがある場合を指定するのに対し、(a) -[]-> (b)は、逆の方向のエッジを探します。

次の例は、AprilとChrisの共通の友人で両者よりも年上の人を探します。

SELECT friend.name, friend.dob
FROM MATCH                  /* note the arrow directions below */
  (p1:person) -[:likes]-> (friend) <-[:likes]- (p2:person)
WHERE
  p1.name = 'April' AND p2.name ='Chris' AND
  friend.dob > p1.dob AND friend.dob > p2.dob
ORDER BY friend.dob DESC 

エッジ・パターンのその他の例については、こちらのPGQL仕様の関連するセクションを参照してください。

5.5 PGQLによる頂点およびエッジ・ラベル

ラベルは、グラフのエッジおよびノードにタイプ情報を添付する方法で、すべてのノードが同じものを表しているわけではないグラフの制約に使用することができます。次に例を示します。

SELECT p.name
FROM MATCH (p:person) -[e1:likes]-> (m1:movie),
     MATCH (p) -[e2:likes]-> (m2:movie)
WHERE m1.title = 'Star Wars'
  AND m2.title = 'Avatar'

ラベル式のその他の例については、こちらのPGQL仕様の関連するセクションを参照してください。

5.6 PGQLによる可変長のパス

可変長パス・パターンは、頂点およびエッジの変数と一致するように*のような数量詞を持ちます。PATHマクロを使用すると、名前を参照することにより、MATCH句に任意の回数だけ埋め込むことのできる問合せの開始時に、名前付きパス・パターンを指定できます。次の例は、MarioとLuigiの共通の祖先をすべて検索します。

PATH has_parent AS () -[:has_father|has_mother]-> ()
SELECT ancestor.name
FROM MATCH (p1:Person) -/:has_parent*/-> (ancestor:Person)
   , MATCH (p2:Person) -/:has_parent*/-> (ancestor)
WHERE
  p1.name = 'Mario' AND
  p2.name = 'Luigi'

追加の制約または問合せ結果では使用されない、中間エッジまたはノードの名前は定義する必要がないため、前のパス仕様では、匿名制約の使用も示しています。匿名要素は、[:has_father|has_mother]などの制約を持つことができます。エッジは変数名を取得できません(他の場所で参照されないため)が、制約はされます。

可変長のパス・パターン一致のその他の例については、こちらのPGQL仕様の関連するセクションを参照してください。

5.7 PGQLによる集計およびソート

SQLのように、PGQLは次のサポートがあります。

  • ソリューションのグループを作成するGROUP BY

  • MIN、MAX、SUMおよびAVG集計

  • 結果をソートするORDER BY

および、その他の多くのなじみのあるSQL構造がサポートされます。

GROUP BYおよび集計については、こちらのPGQL仕様の関連するセクションを参照してください。ORDER BYについては、こちらのPGQL仕様の関連するセクションを参照してください。

5.8 インメモリー・グラフ・サーバー(PGX)に対するPGQL問合せの実行

このセクションでは、インメモリー・グラフ・サーバー(PGX)でPGQL問合せを実行するために使用されるJava APIについて説明します。

5.8.1 PGQLスタート・ガイド

このセクションでは、PGQLの開始方法の例を示します。事前に設定されているデータベース・レルムを想定しています(3.1.1「データベース認証のためのグラフ・サーバーの準備」のステップに従ってください)。また、ユーザーがHRスキーマへのreadアクセス権を持っていることも想定しています。

最初に、CREATE PROPERTY GRAPH文を実行して、従業員、部門およびemployee works at departmentを含むグラフを作成します。

例5-1 インメモリー・グラフ・サーバー(PGX)でのグラフの作成

次の文は、インメモリー・グラフ・サーバー(PGX)にグラフを作成します

String statement =
      "CREATE PROPERTY GRAPH hr_simplified "
    + "  VERTEX TABLES ( "
    + "    hr.employees LABEL employee "
    + "      PROPERTIES ARE ALL COLUMNS EXCEPT ( job_id, manager_id, department_id ), "
    + "    hr.departments LABEL department "
    + "      PROPERTIES ( department_id, department_name ) "
    + "  ) "
    + "  EDGE TABLES ( "
    + "    hr.employees AS works_at "
    + "      SOURCE KEY ( employee_id ) REFERENCES employees "
    + "      DESTINATION departments "
    + "      PROPERTIES ( employee_id ) "
    + "  )";
session.executePgql(statement);

/**
 * To get a handle to the graph, execute:
 */
PgxGraph g = session.getGraph("HR_SIMPLIFIED");

/**
 * You can use this handle to run PGQL queries on this graph.
 * For example, to find the department that “Nandita Sarchand” works for, execute:
 */
String query =
    "SELECT dep.department_name "
  + "FROM MATCH (emp:Employee) -[:works_at]-> (dep:Department) "
  + "WHERE emp.first_name = 'Nandita' AND emp.last_name = 'Sarchand' "
  + "ORDER BY 1";
PgqlResultSet resultSet = g.queryPgql(query);
resultSet.print();
+-----------------+
| department_name |
+-----------------+
| Shipping        |
+-----------------+

/**
 * To get an overview of the types of vertices and their frequencies, execute:
 */
String query =
      "SELECT label(n), COUNT(*) "
    + "FROM MATCH (n) "
    + "GROUP BY label(n) "
    + "ORDER BY COUNT(*) DESC";
PgqlResultSet resultSet = g.queryPgql(query);
resultSet.print();

+-----------------------+
| label(n)   | COUNT(*) |
+-----------------------+
| EMPLOYEE   | 107      |
| DEPARTMENT | 27       |
+-----------------------+

/**
  *To get an overview of the types of edges and their frequencies, execute:
  */
 String query =
    "SELECT label(n) AS srcLbl, label(e) AS edgeLbl, label(m) AS dstLbl, COUNT(*) "
  + "FROM MATCH (n) -[e]-> (m) "
  + "GROUP BY srcLbl, edgeLbl, dstLbl "
  + "ORDER BY COUNT(*) DESC";
PgqlResultSet resultSet = g.queryPgql(query);
resultSet.print();

+---------------------------------------------+
| srcLbl   | edgeLbl  | dstLbl     | COUNT(*) |
+---------------------------------------------+
| EMPLOYEE | WORKS_AT | DEPARTMENT | 106      |
+---------------------------------------------+

5.8.2 サポートされているPGQL機能

インメモリー・グラフ・サーバー(PGX)は、DROP PROPERTY GRAPHを除くすべてのPGQL機能をサポートしています。

次に説明されている特定の制限がある機能はほとんどありません。

5.8.2.1 数量詞の制限

到達可能性パターンでは、*+{1,4}などのすべての数量詞がサポートされていますが、最短で最小コストのパス・パターンでサポートされている数量詞は* (ゼロ以上)のみです。

5.8.2.2 定量化されたパターンでのWHERE句およびCOST句の制限事項

到達可能性パターンや最短パス・パターンおよび最小コスト・パス・パターンなどの定量化されたパターンのWHERE句およびCOST句は、単一の変数の参照のみに制限されます。

次に、WHERE句またはCOST句が、zeroまたはoneのかわりに2つの変数eおよびxを参照しているためにサポートされていない問合せの例を示します。

... PATH p AS (n) –[e]-> (m) WHERE e.prop > m.prop ...
... SHORTEST ( (n) (-[e]-> (x) WHERE e.prop + x.prop > 10)* (m) ) ...
... CHEAPEST ( (n) (-[e]-> (x) COST e.prop + x.prop )* (m) ) ...

次の問合せがサポートされているのは、副問合せは外部スコープから単一の変数aのみを参照し、変数cは副問合せで新たに導入されたためにカウントされないためです。

... PATH p AS (a) -> (b)
      WHERE EXISTS ( SELECT * FROM MATCH (a) -> (c) ) ...

5.8.3 CREATE PROPERTY GRAPH文を実行するためのJava API

CREATE PROPERTY GRAPH文を実行する最も簡単な方法は、PgxSession.executePgql(String statement)メソッドを使用することです。

例5-2 CREATE PROPERTY GRAPH文の実行

String statement =
      "CREATE PROPERTY GRAPH hr_simplified "
    + "  VERTEX TABLES ( "
    + "    hr.employees LABEL employee "
    + "      PROPERTIES ARE ALL COLUMNS EXCEPT ( job_id, manager_id, department_id ), "
    + "    hr.departments LABEL department "
    + "      PROPERTIES ( department_id, department_name ) "
    + "  ) "
    + "  EDGE TABLES ( "
    + "    hr.employees AS works_at "
    + "      SOURCE KEY ( employee_id ) REFERENCES employees "
    + "      DESTINATION departments "
    + "      PROPERTIES ( employee_id ) "
    + "  )";
session.executePgql(statement);
PgxGraph g = session.getGraph("HR_SIMPLIFIED");

/**
 * Alternatively, one can use the prepared statement API, for example:
 */

PgxPreparedStatement stmnt = session.preparePgql(statement);
stmnt.execute();
stmnt.close();
PgxGraph g = session.getGraph("HR_SIMPLIFIED");

5.8.4 SELECT問合せを実行するためのJava API

このセクションでは、インメモリー・グラフ・サーバー(PGX)でSELECT問合せを実行するためのAPIについて説明します。

5.8.4.1 インメモリー・グラフ・サーバー(PGX)のグラフに対するSELECT問合せの実行

PgxGraph.queryPgql(String query)メソッドは、PgxGraphの作成に使用されたセッションで問合せを実行します。このメソッドは、PgqlResultSetを返します。

問合せはPGXグラフに対して直接実行されるため、MATCH句内のON句は省略できます。同じ理由で、INSERT句内のINTO句は省略できます。ただし、ON句およびINTO句でグラフ名を明示的に指定する場合、これらのグラフ名がグラフの実際の名前(PgxGraph.getName())と一致している必要があります。

5.8.4.2 PGXセッションに対するSELECT問合せの実行

PgxSession.queryPgql(String query)メソッドは、セッションで指定された問合せを実行し、PgqlResultSetを返します。

MATCH句内のON句、およびINSERT句内のINTO句は、指定する必要があり、省略できません。この時点では、単一の問合せで複数のグラフのデータを結合することはまだサポートされていないため、問合せのすべてのON句およびINTO句で同じグラフを参照する必要があります。

5.8.4.3 結果セットの反復処理

結果セットを反復処理するには、JDBCに似た方法またはJava Iteratorインタフェースを使用する方法があります。

JDBCのような反復の場合、PgqlResultSet (パッケージoracle.pgx.api)のメソッドはjava.sql.ResultSetのメソッドと似ています。主な違いは、PGQLの結果セット・インタフェースはJava 8で導入された新しい日時ライブラリに基づいているのに対し、java.sql.ResultSetはレガシーjava.util.Dateに基づいていることです。ギャップを埋めるために、PGQLの結果セットは、java.util.Dateをまだ使用しているアプリケーションにgetLegacyDate(..)を提供します。

PgqlResultSetには、最初の行の前に初期設定されるcursorがあります。その後、次のメソッドを使用してカーソルを再配置できます。
  • next() : boolean
  • previous() : boolean
  • beforeFirst()
  • afterLast()
  • first() : boolean
  • last() : boolean
  • absolute(long row) : boolean
  • relative(long rows) : boolean

前述のメソッドの詳細は、ここを参照してください。

カーソルが目的の行に置かれると、次のgetterを使用して値が取得されます。
  • getObject(int columnIdx) : Object
  • getObject(String columnName) : Object
  • getString(int columnIdx) : String
  • getString(String columnName) : String
  • getInteger(int columnIdx) : Integer
  • getInteger(String columnName) : Integer
  • getLong(int columnIdx) : Long
  • getLong(String columnName) : Long
  • getFloat(int columnIdx) : Float
  • getFloat(String columnName) : Float
  • getDouble(int columnIdx) : Double
  • getDouble(String columnName) : Double
  • getBoolean(int columnIdx) : Boolean
  • getBoolean(String columnName) : Boolean
  • getVertexLabels(int columnIdx) : Set<String>
  • getVertexLabels(String columnName) : Set<String>
  • getDate(int columnIdx) : LocalDate
  • getDate(String columnName) : LocalDate
  • getTime(int columnIdx) : LocalTime
  • getTime(String columnName) : LocalTime
  • getTimestamp(int columnIdx) : LocalDateTime
  • getTimestamp(String columnName) : LocalDateTime
  • getTimeWithTimezone(int columnIdx) : OffsetTime
  • getTimeWithTimezone(String columnName) : OffsetTime
  • getTimestampWithTimezone(int columnIdx) : OffsetDateTime
  • getTimestampWithTimezone(String columnName) : OffsetDateTime
  • getLegacyDate(int columnIdx) : java.util.Date
  • getLegacyDate(String columnName) : java.util.Date
  • getList(int columnIdx) : List<T>
  • getList(String columnName) : List<T>

前述のメソッドの詳細は、ここを参照してください。

最後に、結果セットのリソースを解放するPgqlResultSet.close()があり、PgqlResultSet.getMetaData()を使用して列名と列数を取得できます。

結果セットの反復の例は次のとおりです。

PgqlResultSet resultSet = g.queryPgql(
    "   SELECT owner.name AS account_holder, SUM(t.amount) AS total_transacted_with_Nikita "
  + "     FROM MATCH (p:Person) -[:ownerOf]-> (account1:Account) " 
  + "        , MATCH (account1) -[t:transaction]- (account2) "
  + "        , MATCH (account2:Account) <-[:ownerOf]- (owner:Person|Company) "
  + "    WHERE p.name = 'Nikita' "
  + " GROUP BY owner");

while (resultSet.next()) {
  String accountHolder = resultSet.getString(1);
  long totalTransacted = resultSet.getLong(2);
  System.out.println(accountHolder + ": " + totalTransacted);
}

resultSet.close();

前述の例の出力は次のようになります。

Oracle: 4501
Camille: 1000

さらに、PgqlResultSetもJava Iteratorインタフェースを介して反復可能です。結果セットに対する"for each loop"の例は、次のとおりです。

for (PgxResult result : resultSet) {
  String accountHolder = result.getString(1);
  long totalTransacted = result.getLong(2);
  System.out.println(accountHolder + ": " + totalTransacted);
}

前述の例の出力は次のようになります。

Oracle: 4501
Camille: 1000

PgqlResultSetで使用可能なものと同じgetterをPgxResultでも使用できることに注意してください。

5.8.4.4 結果セットの出力

PgqlResultSet (パッケージoracle.pgx.api)の次のメソッドは、結果セットを出力するために使用されます。

  • print() : PgqlResultSet
  • print(long numResults) : PgqlResultSet
  • print(long numResults, int from) : PgqlResultSet
  • print(PrintStream printStream, long numResults, int from) : PgqlResultSet

次に例を示します。

g.queryPgql("SELECT COUNT(*) AS numPersons FROM MATCH (n:Person)").print().close()
+------------+
| numPersons |
+------------+
| 3          |
+------------+

もう1つ例を示します。

PgqlResultSet resultSet = g.queryPgql(
    "   SELECT owner.name AS account_holder, SUM(t.amount) AS total_transacted_with_Nikita "
  + "     FROM MATCH (p:Person) -[:ownerOf]-> (account1:Account) " 
  + "        , MATCH (account1) -[t:transaction]- (account2) "
  + "        , MATCH (account2:Account) <-[:ownerOf]- (owner:Person|Company) "
  + "    WHERE p.name = 'Nikita' "
  + " GROUP BY owner")

resultSet.print().close()
+-----------------------------------------------+
| account_holder | total_transacted_with_Nikita |
+-----------------------------------------------+
| Camille        | 1000.0                       |
| Oracle         | 4501.0                       |
+-----------------------------------------------+

5.8.5 UPDATE問合せを実行するためのJava API

UPDATE問合せは、PGQL 1.3仕様のグラフの変更のセクションで説明されているように、INSERTUPDATEおよびDELETE操作を使用して既存のグラフに変更を加えます。

INSERTを使用するとグラフに新しい頂点およびエッジを挿入でき、UPDATEを使用するとプロパティを新しい値に設定して既存の頂点およびエッジを更新でき、DELETEを使用するとグラフから頂点およびエッジを削除できます。

5.8.5.1 インメモリー・グラフ・サーバー(PGX)のグラフに対するUPDATE問合せの実行

グラフに対してUPDATE問合せを実行するには、PgxGraph.executePgql(String query)メソッドを使用します。

次に、INSERT問合せの例を示します。

g.executePgql("INSERT VERTEX v " +
              "         LABELS ( Person ) " +
              "         PROPERTIES ( v.firstName = 'Camille', " +
              "                      v.lastName = ' Mullins' ) "); 

INSERTINTO句は省略できることに注意してください。INTO句を使用する場合、INTO句のグラフ名は、問合せが実行されるPGXグラフ(PgxGraph.getName())の名前に対応している必要があります。

次に、UPDATE問合せの例を示します。

// set the date of birth of Camille to 2014-11-15
g.executePgql("UPDATE v SET ( v.dob = DATE '2014-11-14' ) " +
              "FROM MATCH (v:Person) " +
              "WHERE v.firstName = 'Camille' AND v.lastName = ' Mullins' "); 

次に、DELETE問合せの例を示します。

// delete Camille from the graph
g.executePgql("DELETE v " +
              "FROM MATCH (v:Person) " +
              "WHERE v.firstName = 'Camille' AND v.lastName = 'Mullins' "); 
5.8.5.2 PGXセッションに対するUPDATE問合せの実行

現時点では、PgxSessionに対するUPDATE問合せの実行はサポートされていないため、更新は常にPgxGraphに対して実行する必要があります。セッションからグラフを取得するには、PgxSession.getGraph(String graphName)メソッドを使用します。

5.8.5.3 PGQLによるグラフの更新可能性

Oracle RDBMSまたはCSVファイルからPGXにロードされたグラフ・データは、PGQLを介してすぐに更新することはできません。

最初に、PgxGraph.clone()メソッドを使用してデータのコピーを作成する必要があります。結果のグラフは完全に更新可能です。

次のケースについて検討します。

// load a graph from the RDBMS or from CSV
PgxGraph g1 = session.readGraphWithProperties("path/to/graph_config.json");

// create an updatable copy of the graph
PgxGraph g2 = g1.clone("new_graph_name");

// insert an additional vertex into the graph
g2.executePgql("INSERT VERTEX v " +
               "         LABELS ( Person ) " +
               "         PROPERTIES ( v.firstName = 'Camille', " +
               "                      v.lastName = ' Mullins')"); 

さらに、前述の例の最後の2つのステップを組み合せて単一のステップにするPgxGraph.cloneAndExecutePgql(String query, String graphName)メソッドもあります。

// create an updatable copy of the graph while inserting a new vertex
PgxGraph g2_copy = g1.cloneAndExecutePgql(
                     "INSERT VERTEX v " +
                     "         LABELS ( Person ) " +
                     "         PROPERTIES ( v.firstName = 'Camille', " +
                     "                      v.lastName = ' Mullins') "
                   , "new_graph_name");

PgxGraph.clone()を介して作成されるグラフは、セッションに対してローカルであることに注意してください。ただし、PgxGraph.publish(..)メソッドを介して他のセッションと共有することはできますが、PGQLを使用して更新することはできなくなりました。セッションローカル・グラフのみが更新可能ですが、永続グラフは更新できません。

5.8.5.4 グラフの基礎となるスキーマの変更

INSERT操作では、既知のラベルとプロパティを持つ頂点とエッジのみを挿入できます。同様に、UPDATE操作では、既知のプロパティの値のみを設定できます。したがって、新しいデータは常にグラフの既存のスキーマに準拠する必要があります。

ただし、グラフのスキーマを更新するためのPGX APIはいくつか存在します。新しいラベルを追加するためのAPIは存在しませんが、PgxGraph.createVertexProperty(PropertyType type, String name)およびPgxGraph.createEdgeProperty(PropertyType type, String name)メソッドを介して新しいプロパティを追加できます。新しいプロパティは、ラベルに関係なく、グラフ内の各頂点/エッジにアタッチされます。最初はプロパティにデフォルト値が割り当てられますが、その後UPDATE文を介して値を更新できます。

次のケースについて検討します。

// load a graph from the RDBMS or from CSV
PgxGraph g = session.readGraphWithProperties("path/to/graph_config.json");

// add a new property to the graph
g.createVertexProperty(PropertyType.LOCAL_DATE, "dob");

// set the date of birth of Camille to 2014-11-15
g.executePgql("UPDATE v SET ( v.dob = DATE '2014-11-14' ) " +
              "FROM MATCH (v:Person) " +
              "WHERE v.firstName = 'Camille' AND v.lastName = ' Mullins' ");

5.8.6 PGQL問合せを実行するためのセキュリティ・ツール

問合せインジェクションから保護するために、リテラルのかわりにバインド変数を使用し、グラフ名、ラベル、プロパティ名などの識別子のかわりにprintIdentifier(String identifier)を使用できます。

5.8.6.1 バインド変数の使用

バインド変数を使用する理由は2つあります。

  • 問合せインジェクションから保護します。
  • 問合せの再コンパイルを必要とせずに同じバインド変数を複数回設定できるため、問合せの実行が高速化されます。

プリペアド文を作成するには、次の2つの方法のいずれかを使用します。

  • PgxGraph.preparePgql(String query) : PgxPreparedStatement
  • PgxSession.preparePgql(String query) : PgxPreparedStatement

これらのメソッドから返されるPgxPreparedStatement (package oracle.pgx.api)には、バインド変数を指定されたデータ型の値にバインドするためのsetterメソッドがあります。

次のケースについて検討します。
PreparedStatement stmnt = g.preparePgql(
  "SELECT v.id, v.dob " +
  "FROM MATCH (v) " +
  "WHERE v.firstName = ? AND v.lastName = ?");
stmnt.setString(1, "Camille");
stmnt.setString(2, "Mullins");
ResultSet rs = stmnt.executeQuery();

問合せ内の各バインド変数は、PgxPreparedStatementの次のセッターのいずれかを使用して値に設定する必要があります。

  • setBoolean(int parameterIndex, boolean x)
  • setDouble(int parameterIndex, double x)
  • setFloat(int parameterIndex, float x)
  • setInt(int parameterIndex, int x)
  • setLong(int parameterIndex, long x)
  • setDate(int parameterIndex, LocalDate x)
  • setTime(int parameterIndex, LocalTime x)
  • setTimestamp(int parameterIndex, LocalDateTime x)
  • setTimeWithTimezone(int parameterIndex, OffsetTime x)
  • setTimestampWithTimezone(int parameterIndex, OffsetDateTime x)
  • setArray(int parameterIndex, List<?> x)

すべてのバインド変数が設定されると、文は次の方法で実行できます。

  • PgxPreparedStatement.executeQuery()
    • SELECT問合せの場合のみ
    • ResultSetを返します
  • PgxPreparedStatement.execute()
    • あらゆるタイプの文の場合
    • 結果の形式を示すブール値を返します。SELECT問合せの場合はtrue、それ以外の場合はfalseです。
    • SELECTの場合、ResultSetには後でPgxPreparedStatement.getResultSet()を使用してアクセスできます。

PGQLでは、配列リテラルを含む任意のデータ型のリテラルのかわりにバインド変数を使用できます。文字列配列のインスタンスにバインド変数が設定されている問合せの例は次のとおりです。

List<String> countryNames = new ArrayList<String>();
countryNames.add("Scotland");
countryNames.add("Tanzania");
countryNames.add("Serbia");

PreparedStatement stmnt = g.preparePgql(
  "SELECT n.name, n.population " +
  "FROM MATCH (c:Country) " +
  "WHERE c.name IN ?");

ResultSet rs = stmnt.executeQuery();

最後に、プリペアド文が不要になった場合は、PgxPreparedStatement.close()を介してクローズされ、リソースが解放されます。

5.8.6.2 安全な方法での識別子の使用

文字列の連結を介して問合せを作成すると、問合せのリテラルのみでなく、グラフ名、ラベル、プロパティ名などの識別子でもセキュリティ上のリスクが生じます。唯一の問題は、バインド変数がそのような識別子でサポートされていないことです。したがって、これらの識別子がアプリケーションの観点から可変である場合は、oracle.pgql.lang.ir.PgqlUtils.printIdentifier(String identifier)メソッドを介して識別子を渡すことにより、問合せインジェクションから保護することをお薦めします。

識別子文字列を指定すると、メソッドは識別子の先頭と末尾に二重引用符を自動的に追加し、識別子内の文字を適切にエスケープします。

次のケースについて検討します。

String graphNamePrinted = printIdentifier("my graph name with \" special % characters ");
PreparedStatement stmnt = g.preparePgql(
  "SELECT COUNT(*) AS numVertices FROM MATCH (v) ON " + graphNamePrinted);

5.8.7 PGQL問合せをチューニングするためのベスト・プラクティス

このセクションでは、メモリー割当て、並列度および問合せ計画に関するベスト・プラクティスについて説明します。

5.8.7.1 メモリー割当て

インメモリー・アナリスト(PGX)にはon-heapおよびoff-heapメモリーがあり、前者は標準JVMヒープですが、後者はPGXによって管理される別のヒープです。グラフ・データと同様に、PGQL問合せの中間結果および最終結果は、一部がオンヒープおよび一部がオフヒープで格納されます。したがって、両方のヒープが必要です。

オンヒープ・メモリーの場合、JVMの起動時にデフォルトの最大値が選択されますが、-Xmxオプションを介して上書きできます。

オフヒープの場合、デフォルトでは最大値は設定されず、オフヒープのメモリー使用量はシステム・リソースを使い果たすまで自動的に増加し続けます。システム・リソースがなくなると、操作が取り消され、メモリーが解放され、適切な例外がユーザーに渡されます。必要に応じて、PGXのmax_off_heap_sizeオプションを介して最大オフヒープ・サイズを構成できます。

可能なかぎり最大のグラフをロードして問合せを実行できるようにするための適切な開始点として、オンヒープ・とオフヒープの比率を1:1にすることをお薦めします。たとえば、マシンで256GBのメモリーが使用可能な場合、最大オンヒープを125GBに設定すると、同様の量のメモリーがオフヒープに使用可能になります。

export JAVA_OPTS="-Xmx125g"
5.8.7.2 パラレル化

デフォルトでは、使用可能なすべてのプロセッサ・スレッドがPGQL問合せの処理に使用されます。ただし、必要に応じてインメモリー・アナリスト(PGX)の並列度オプションを設定することにより、スレッドの数を制限できます。

5.8.7.3 問合せ計画の説明

PgxGraph.explainPgql(String query)メソッドは、問合せの問合せ計画を把握するために使用されます。このメソッドは、次のメソッドを持つOperation (package oracle.pgx.api)のインスタンスを返します。

  • print(): 操作とその子操作を出力するため
  • getOperationType(): 操作のタイプを取得するため
  • getPatternInfo(): 操作の文字列表現を取得するため
  • getCostEstimate(): 操作のコストを取得するため
  • getTotalCostEstimate(): 操作とその子操作のコストを取得するため
  • getCardinatlityEstimate(): 予想される結果行数を取得するため
  • getChildren(): 子操作にアクセスするため

次のケースについて検討します。

g.explainPgql("SELECT COUNT(*) FROM MATCH (n) -[e1]-> (m) -[e2]-> (o)").print()
\--- GROUP BY  GroupBy {"cardinality":"42", "cost":"42", "accumulatedCost":"58.1"}
     \--- (m) -[e2]-> (o) NeighborMatch {"cardinality":"3.12", "cost":"3.12", "accumulatedCost":"16.1"}
          \--- (n) -[e1]-> (m) NeighborMatch {"cardinality":"5", "cost":"5", "accumulatedCost":"13"}
               \--- (n) RootVertexMatch {"cardinality":"8", "cost":"8", "accumulatedCost":"8"}

前述の例では、print()メソッドを使用して問合せ計画を出力しています。

問合せ計画が最適でない場合、パフォーマンスを向上させるために問合せを書き直せることがよくあります。たとえば、合計実行時間を改善する方法として、SELECT問合せをUPDATEおよびSELECT問合せに分割できます。

インメモリー・アナリスト(PGX)はヒント・メカニズムを提供しないことに注意してください。

5.9 Oracle Databaseに対して直接PGQL問合せを実行

このトピックでは、(インメモリーと対照的に)Oracle Database内のグラフに対して直接PGQL問合せを実行する方法について説明します。

プロパティ・グラフ問合せ言語(PGQL)の問合せは、Oracle Databaseに格納されているディスク常駐プロパティ・グラフ・データに対して実行できます。Oracle Database (RDBMS)上のPGQLには、PGQL問合せを実行するためのJava APIが用意されています。RDBMS上のPGQL内のロジックは発行されたPGQL問合せを同等のSQL問合せに変換し、結果として作成されたSQLがデータベース・サーバーで実行されます。次に、RDBMS上のPGQLは、便利なPGQL結果セットAPIでSQL問合せの結果をラップします。

このPGQL問合せの実行フローは次の図のようになります。

図5-1 Oracle Database (RDBMS)上のPGQL

図5-1の説明を次に示します
「図5-1 Oracle Database (RDBMS)上のPGQL」の説明

基本的な実行フローは次のとおりです。

  1. PGQL問合せはJava APIを介してRDBMS上のPGQLに送信されます。

  2. PGQL問合せはSQLに変換されます。

  3. 変換されたSQLは、JDBCによってOracle Databaseに送信されます。

  4. SQL結果セットは、PGQL結果セットとしてラップされてコール元に返されます。

Oracle Databaseに格納されたプロパティ・グラフ・データに対してPGQL問合せを直接実行できることにより、いくつかの利点があります。

  • PGQLでは、VT$、VD$、GE$、およびGT$を含めた問合せスキーマ表に手動で書き込まれるSQLよりも、より自然なグラフ問合せの表現方法が提供されています。

  • PGQL問合せは、グラフ・データのスナップショットをPGXにロードせずに実行できます。そのため、頻繁に更新されるグラフ・データの失効について心配する必要がありません。

  • PGQL問合せは、大きすぎてメモリー内に収まらないグラフ・データに対して実行できます。

  • 堅牢でスケーラブルなOracle SQLエンジンを、PGQL問合せの実行に使用できます。

  • Oracle Databaseの管理、監視、およびチューニングを行うための成熟したツールを、PGQL問合せのチューニングと監視に使用できます。

5.9.1 サポートされるPGQL機能

PGQLは、プロパティ・グラフ・データの問合せを行うための、SQLに似た問合せ言語です。これは、グラフのパターン一致の概念に基づいており、これを使用して、特にトポロジ制約、パス、フィルタ、ソート、および集計を指定できます。

oracle.pg.rdbms.pgqlパッケージに定義されているPGQL用のJava APIは、いくつかの例外付きでPGQL 仕様をサポートします。(PGQL仕様については、https://pgql-lang.orgを参照してください)。

PGQL の次の機能はサポートされません。

  • 最短パス
  • ARRAY_AGG集計
  • INおよびNOT IN述語
  • COST関数を使用した単一のCHEAPESTパスとTOP-K CHEAPESTパス
  • ラベルとプロパティに対する大文字の参照の大文字と小文字を区別しない一致

さらに、PGQL の次の機能には特別な注意が必要です。

5.9.1.1 Temporal型

Temporal型のDATE、TIMESTAMP、およびTIMESTAMP WITH TIMEZONEがPGQL問合せでサポートされています。

これらのすべての値型は、Oracle SQLのTIMESTAMP WITH TIME ZONE型を使用して内部で表されます。DATEの値は、UTC+0のタイムゾーンで最も早い時刻と想定して、自動的にTIMESTAMP WITH TIME ZONEに変換されます(たとえば、2000-01-01は2000-01-01 00:00:00.00+00:00になります)。TIMESTAMPの値は、UTC+0タイムゾーンと想定して、自動的にTIMESTAMP WITH TIME ZONEに変換されます(たとえば、2000-01-01 12:00:00.00は2000-01-01 12:00:00.00+00:00)。

Temporalの定数は、PGQL問合せに次のように書き込まれます。

  • DATE 'YYYY-MM-DD'

  • TIMESTAMP 'YYYY-MM-DD HH24:MI:SS.FF'

  • TIMESTAMP WITH TIMEZONE 'YYYY-MM-DD HH24:MI:SS.FFTZH:TZM'

例としては、DATE '2000-01-01'、TIMESTAMP '2000-01-01 14:01:45.23'、TIMESTAMP WITH TIMEZONE '2000-01-01 13:00:00.00-05:00'、およびTIMESTAMP WITH TIMEZONE '2000-01-01 13:00:00.00+01:00'などがあります。

また、Temporal値は、String値をTemporal型にキャストすることで取得できます。サポートされている文字列形式は次のとおりです。

  • DATE 'YYYY-MM-DD'

  • TIMESTAMP 'YYYY-MM-DD HH24:MI:SS.FF'および'YYYY-MM-DD"T"HH24:MI:SS.FF'

  • TIMESTAMP WITH TIMEZONE 'YYYY-MM-DD HH24:MI:SS.FFTZH:TZM'および'YYYY-MM-DD"T"HH24:MI:SS.FFTZH:TZM'

例としては、CAST ('2005-02-04' AS DATE)、CAST ('1990-01-01 12:00:00.00' AS TIMESTAMP)、CAST ('1985-01-01T14:05:05.00-08:00' AS TIMESTAMP WITH TIMEZONE)などがあります。

PgqlResultSetオブジェクトからの結果を使用する場合、getObjectはTemporal型のjava.sql.Timestampオブジェクトを返します。

バインド変数は、PGQLではTIMESTAMP WITH TIMEZONEのTemporal型にのみ使用でき、java.sql.Timestampオブジェクトを入力として取るsetTimestampメソッドがバインド値を設定するために使用されます。より単純な別の方法として、CAST文で文字列のバインド変数を使用してTemporal値(たとえば、CAST (? AS TIMESTAMP WITH TIMEZONE)とそれに続くsetString(1, "1985-01-01T14:05:05.00-08:00"))をバインドできます。バインド変数の詳細は、「PGQL問合せでのバインド変数の使用」も参照してください。

5.9.1.2 型キャスト

型キャストは、SQLスタイルCAST (VALUE AS DATATYPE)構文を使用するPGQLでサポートされます。たとえば、CAST ('25' AS INT)、CAST (10 AS STRING)、CAST ('2005-02-04' AS DATE)、CAST (e.weight AS STRING)などです。サポートされているキャスト操作を次の表にまとめます。Yは、変換がサポートされることを示し、Nは、変換がサポートされないことを示します。無効な値に対するキャスト操作(たとえば、CAST ('xyz' AS INT))またはサポートされない変換(たとえば、CAST (10 AS TIMESTAMP))では、SQL例外ではなく、NULLが返されます。

表5-1 PGQLでの型キャストのサポート(変換元の型および変換先の型)

"変換先"の型 STRINGから INTから LONGから FLOATから DOUBLEから BOOLEANから DATEから TIMESTAMPから TIMESTAMP WITH TIMEZONEから

STRINGへ

Y

Y

Y

Y

Y

Y

Y

Y

Y

INTへ

Y

Y

Y

Y

Y

Y

N

N

N

LONGへ

Y

Y

Y

Y

Y

Y

N

N

N

FLOATへ

Y

Y

Y

Y

Y

Y

N

N

N

DOUBLEへ

Y

Y

Y

Y

Y

Y

N

N

N

BOOLEANへ

Y

Y

Y

Y

Y

Y

N

N

N

DATEへ

Y

N

N

N

N

N

Y

Y

Y

TIMESTAMPへ

Y

N

N

N

N

N

Y

Y

Y

TIMESTAMP WITH TIMEZONEへ

Y

N

N

N

N

N

Y

Y

Y

型キャストを使用する問合せ例を次に示します。

SELECT e.name, CAST (e.birthDate AS STRING) AS dob
FROM MATCH (e)
WHERE e.birthDate < CAST ('1980-01-01' AS DATE)
5.9.1.3 CONTAINS組込み関数

CONTAINS組込み関数がサポートされています。これは、Oracle Text索引とともに、頂点プロパティとエッジ・プロパティに対して使用されます。CONTAINSは、値がOracle Text検索文字列に一致するとtrueを返し、一致しないとfalseを返します。

問合せ例は次のとおりです。

SELECT v.name
FROM MATCH (v)
WHERE CONTAINS(v.abstract, 'Oracle')

PGQLでの全文索引の使用の詳細は、「PGQL問合せでのテキスト索引の使用」も参照してください。

5.9.2 CREATE PROPERTY GRAPH文によるプロパティ・グラフの作成

PGQLを使用して、リレーショナル・データベース表からプロパティ・グラフを作成できます。CREATE PROPERTY GRAPH文では、頂点に変換される一連の頂点の表と、エッジに変換される一連のエッジの表を定義します。各表にはキー、ラベルおよび一連の列プロパティを指定できます。列タイプCHAR、NCHAR、VARCHAR、VARCHAR2、NVARCHAR2、NUMBER、LONG、FLOAT、DATE、TIMESTAMPおよびTIMESTAMP WITH TIMEZONEは、CREATE PROPERTY GRAPH列プロパティでサポートされています。

CREATE PROPERTY GRAPH文がコールされると、グラフのプロパティ・グラフ・スキーマが作成され、データがソース表からプロパティ・グラフ・スキーマ表にコピーされます。グラフは1回かぎりのコピーとして作成され、ソース・データとの同期は自動的には保持されません。

例5-3 PgqlCreateExample1.java

この例は、一連のリレーショナル表からプロパティ・グラフを作成する方法を示しています。この例では表Person、HobbyおよびHobbiesが作成されるため、この例を実行する前にはこれらの表が存在しないことに注意してください。この例は、プロパティ・グラフに対して問合せを実行する方法も示しています。

import java.sql.Connection;
import java.sql.Statement;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to create a Property Graph from relational
 * data stored in Oracle Database executing a PGQL create statement.
 */
public class PgqlCreateExample1
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    Statement stmt = null;
    PgqlStatement pgqlStmt = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();
      conn.setAutoCommit(false);

      // Create relational data
      stmt = conn.createStatement();

      //Table Person
      stmt.executeUpdate(
        "create table Person(  " +
        "  id    NUMBER,       " +
        "  name  VARCHAR2(20), " +
        "  dob   TIMESTAMP     " +
        ")");

      // Insert some data
      stmt.executeUpdate("insert into Person values(1,'Alan', DATE '1995-05-26')");
      stmt.executeUpdate("insert into Person values(2,'Ben', DATE '2007-02-15')");
      stmt.executeUpdate("insert into Person values(3,'Claire', DATE '1967-11-30')");

      // Table Hobby
      stmt.executeUpdate(
        "create table Hobby(   " + 
        "  id    NUMBER,       " +
        "  name  VARCHAR2(20)  " +
        ")");

      // Insert some data
      stmt.executeUpdate("insert into Hobby values(1, 'Sports')");
      stmt.executeUpdate("insert into Hobby values(2, 'Music')");

      // Table Hobbies
      stmt.executeUpdate(
        "create table Hobbies( "+
        "  person    NUMBER, "+
        "  hobby     NUMBER, "+
        "  strength  NUMBER  "+
        ")");

      // Insert some data
      stmt.executeUpdate("insert into Hobbies values(1, 1, 20)");
      stmt.executeUpdate("insert into Hobbies values(1, 2, 30)");
      stmt.executeUpdate("insert into Hobbies values(2, 1, 10)");
      stmt.executeUpdate("insert into Hobbies values(3, 2, 20)");

      //Commit changes
      conn.commit();

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);

      // Create a PgqlStatement
      pgqlStmt = pgqlConn.createStatement();

      // Execute PGQL to create property graph
      String pgql = 
        "Create Property Graph " + graph + " " +
        "VERTEX TABLES ( " +
        "  Person " +
        "    Key(id) " +
        "    Label \"people\" +
        "    PROPERTIES(name AS \"first_name\", dob AS \"birthday\")," +
        "  Hobby  " +
        "    Key(id) Label \"hobby\"  PROPERTIES(name AS \"name\")" +
        ")" +
        "EDGE TABLES (" +
        "  Hobbies" +
        "    SOURCE KEY(person) REFERENCES Person " +
        "    DESTINATION KEY(hobby) REFERENCES Hobby " +
        "    LABEL \"likes\" PROPERTIES (strength AS \"score\")" +
        ")";
      pgqlStmt.execute(pgql);

      // Execute a PGQL query to verify Graph creation
      pgql =
        "SELECT p.\"first_name\", p.\"birthday\", h.\"name\", e.\"score\" " +
        "FROM MATCH (p:\"people\")-[e:\"likes\"]->(h:\"hobby\") ON " + graph;
      rs = pgqlStmt.executeQuery(pgql, "");

      // Print the results
      rs.print();
    }
    finally {
      // close the sql statment
      if (stmt != null) {
        stmt.close();
      }
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (pgqlStmt != null) {
        pgqlStmt.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlCreateExample1.javaの出力は次のとおりです。

+---------------------------------------------------------+
|   first_name |   birthday            |   name |   score |
+---------------------------------------------------------+
| Alan         | 1995-05-25 17:00:00.0 | Music  | 30.0    |
| Claire       | 1967-11-29 16:00:00.0 | Music  | 20.0    |
| Ben          | 2007-02-14 16:00:00.0 | Sports | 10.0    |
| Alan         | 1995-05-25 17:00:00.0 | Sports | 20.0    |
+---------------------------------------------------------+

例5-4 PgqlCreateExample2.java

この例は、キーを指定せずにプロパティ・グラフ文を作成する方法を示しています。この例では表Person、HobbyおよびHobbiesが作成されるため、この例を実行する前にはこれらの表が存在しないことに注意してください。

import java.sql.Connection;
import java.sql.Statement;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to create a Property Graph from relational
 * data stored in Oracle Database executing a PGQL create statement.
 */
public class PgqlCreateExample2
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    Statement stmt = null;
    PgqlStatement pgqlStmt = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();
      conn.setAutoCommit(false);

      // Create relational data
      stmt = conn.createStatement();

      //Table Person
      stmt.executeUpdate(
        "create table Person(  " +
        "  id    NUMBER,       " +
        "  name  VARCHAR2(20), " +
        "  dob   TIMESTAMP,    " +
        "  CONSTRAINT pk_person PRIMARY KEY(id)" +
        ")");

      // Insert some data
      stmt.executeUpdate("insert into Person values(1,'Alan', DATE '1995-05-26')");
      stmt.executeUpdate("insert into Person values(2,'Ben', DATE '2007-02-15')");
      stmt.executeUpdate("insert into Person values(3,'Claire', DATE '1967-11-30')");

      // Table Hobby
      stmt.executeUpdate(
        "create table Hobby(   " + 
        "  id    NUMBER,       " +
        "  name  VARCHAR2(20), " +
        "  CONSTRAINT pk_hobby PRIMARY KEY(id)" +
        ")");

      // Insert some data
      stmt.executeUpdate("insert into Hobby values(1, 'Sports')");
      stmt.executeUpdate("insert into Hobby values(2, 'Music')");

      // Table Hobbies
      stmt.executeUpdate(
        "create table Hobbies( "+
        "  person    NUMBER, "+
        "  hobby     NUMBER, "+
        "  strength  NUMBER, "+
        "  CONSTRAINT fk_hobbies1 FOREIGN KEY (person) REFERENCES Person(id), "+
        "  CONSTRAINT fk_hobbies2 FOREIGN KEY (hobby)  REFERENCES Hobby(id)"+
        ")");

      // Insert some data
      stmt.executeUpdate("insert into Hobbies values(1, 1, 20)");
      stmt.executeUpdate("insert into Hobbies values(1, 2, 30)");
      stmt.executeUpdate("insert into Hobbies values(2, 1, 10)");
      stmt.executeUpdate("insert into Hobbies values(3, 2, 20)");

      //Commit changes
      conn.commit();

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);

      // Create a PgqlStatement
      pgqlStmt = pgqlConn.createStatement();

      // Execute PGQL to create property graph
      String pgql = 
        "Create Property Graph " + graph + " " +
        "VERTEX TABLES ( " +
        "  Person " +
        "    Label people +
        "    PROPERTIES ALL COLUMNS," +
        "  Hobby  " +
        "    Label hobby PROPERTIES ALL COLUMNS EXCEPT(id)" +
        ")" +
        "EDGE TABLES (" +
        "  Hobbies" +
        "    SOURCE Person DESTINATION Hobby " +
        "    LABEL likes NO PROPERTIES" +
        ")";
      pgqlStmt.execute(pgql);

      // Execute a PGQL query to verify Graph creation
      pgql =
        "SELECT p.NAME AS person, p.DOB, h.NAME AS hobby " +
        "FROM MATCH (p:people)-[e:likes]->(h:hobby) ON " + graph;
      rs = pgqlStmt.executeQuery(pgql, "");

      // Print the results
      rs.print();
    }
    finally {
      // close the sql statment
      if (stmt != null) {
        stmt.close();
      }
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (pgqlStmt != null) {
        pgqlStmt.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlCreateExample2.javaの出力は次のとおりです。

+-----------------------------------------+
| PERSON |   DOB                 |  HOBBY |
+-----------------------------------------+
| Alan   | 1995-05-25 17:00:00.0 | Music  |
| Claire | 1967-11-29 16:00:00.0 | Music  |
| Ben    | 2007-02-14 16:00:00.0 | Sports |
| Alan   | 1995-05-25 17:00:00.0 | Sports |
+-----------------------------------------+

5.9.3 DROP PROPERTY GRAPH文によるプロパティ・グラフの削除

PGQLを使用してプロパティ・グラフを削除できます。DROP PROPERTY GRAPH文がコールされると、グラフのすべてのプロパティ・グラフ・スキーマ表が削除されます。

例5-5 PgqlDropExample1.java

この例は、プロパティ・グラフを削除する方法を示しています。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to drop a Property executing a PGQL drop statement.
 */
public class PgqlDropExample1
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement pgqlStmt = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();
      conn.setAutoCommit(false);

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);

      // Create a PgqlStatement
      pgqlStmt = pgqlConn.createStatement();

      // Execute PGQL to drop property graph
      String pgql = "Drop Property Graph " + graph;
      pgqlStmt.execute(pgql);

    }
    finally {
      // close the statement
      if (pgqlStmt != null) {
        pgqlStmt.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

5.9.4 oracle.pg.rdbms.pgql Javaパッケージを使用したPGQL問合せの実行

oracle.pg.rdbms.pgqlパッケージのJava APIは、Oracle Databaseに対してPGQL問合せを実行するためのサポートを提供します。このトピックでは、一連の例を使用してJava APIの使用方法について説明します。

ノート:

リリース21cでは、oracle.pg.rdbmsパッケージの次のクラスは非推奨になりました。

oracle.pg.rdbms.OraclePgqlColumnDescriptorImpl
oracle.pg.rdbms.OraclePgqlColumnDescriptor
oracle.pg.rdbms.OraclePgqlExecutionFactory
oracle.pg.rdbms.OraclePgqlExecution
oracle.pg.rdbms.PgqlPreparedStatement
oracle.pg.rdbms.OraclePgqlResultElementImpl
oracle.pg.rdbms.OraclePgqlResultElement
oracle.pg.rdbms.OraclePgqlResultImpl
oracle.pg.rdbms.OraclePgqlResultIterable
oracle.pg.rdbms.OraclePgqlResultIteratorImpl
oracle.pg.rdbms.OraclePgqlResult
oracle.pg.rdbms.OraclePgqlResultSetImpl
oracle.pg.rdbms.OraclePgqlResultSet
oracle.pg.rdbms.OraclePgqlResultSetMetaDataImpl
oracle.pg.rdbms.OraclePgqlResultSetMetaData
oracle.pg.rdbms.PgqlSqlQueryTransImpl
oracle.pg.rdbms.PgqlSqlQueryTrans
oracle.pg.rdbms.PgqlStatement

かわりに、oracle.pg.rdbms.pgql内の同等のクラスを使用する必要があります。

oracle.pg.rdbms.pgql.PgqlColumnDescriptorImpl
oracle.pg.rdbms.pgql.PgqlColumnDescriptor
oracle.pg.rdbms.pgql.PgqlConnection
oracle.pg.rdbms.pgql.PgqlExecution
oracle.pg.rdbms.pgql.PgqlPreparedStatement
oracle.pg.rdbms.pgql.PgqlResultElementImpl
oracle.pg.rdbms.pgql.PgqlResultElement
oracle.pg.rdbms.pgql.PgqlResultSetImpl
oracle.pg.rdbms.pgql.PgqlResultSet
oracle.pg.rdbms.pgql.PgqlResultSetMetaDataImpl
oracle.pg.rdbms.pgql.PgqlSqlTransImpl
oracle.pg.rdbms.pgql.PgqlSqlTrans
oracle.pg.rdbms.pgql.PgqlStatement

oracle.pg.rdbms.OraclePgqlResultSetoracle.pg.rdbms.pgql.PgqlResultSetの違いの1つは、oracle.pg.rdbms.pgql.PgqlResultSetが、頂点オブジェクトおよびエッジ・オブジェクトを取得するためのAPIを提供しないことです。これらのインタフェースを使用している既存のコードは、OracleVertexおよびOracleEdgeオブジェクトではなくIDを射影するように変更する必要があります。OracleVertex.getInstance()またはOracleEdge.getInstance()を呼び出すことで、射影されたID値からOracleVertexまたはOracleEdgeオブジェクトを取得できます。(たとえば、例5-20を参照してください。)

次に示す、Oracleフラット・ファイル形式のtest_graphデータ・セットは、この後のサブトピック内の例で使用されます。このデータ・セットには、1つの頂点ファイル(test_graph.opv)と1つのエッジ・ファイル(test_graph.ope).が含まれています

test_graph.opv:

2,fname,1,Ray,,,person
2,lname,1,Green,,,person
2,mval,5,,,1985-01-01T12:00:00.000Z,person
2,age,2,,41,,person
0,bval,6,Y,,,person
0,fname,1,Bill,,,person
0,lname,1,Brown,,,person
0,mval,1,y,,,person
0,age,2,,40,,person
1,bval,6,Y,,,person
1,fname,1,John,,,person
1,lname,1,Black,,,person
1,mval,2,,27,,person
1,age,2,,30,,person
3,bval,6,N,,,person
3,fname,1,Susan,,,person
3,lname,1,Blue,,,person
3,mval,6,N,,,person
3,age,2,,35,,person

test_graph.ope:

4,0,1,knows,mval,1,Y,,
4,0,1,knows,firstMetIn,1,MI,,
4,0,1,knows,since,5,,,1990-01-01T12:00:00.000Z
16,0,1,friendOf,strength,2,,6,
7,1,0,knows,mval,5,,,2003-01-01T12:00:00.000Z
7,1,0,knows,firstMetIn,1,GA,,
7,1,0,knows,since,5,,,2000-01-01T12:00:00.000Z
17,1,0,friendOf,strength,2,,7,
9,1,3,knows,mval,6,N,,
9,1,3,knows,firstMetIn,1,SC,,
9,1,3,knows,since,5,,,2005-01-01T12:00:00.000Z
10,2,0,knows,mval,1,N,,
10,2,0,knows,firstMetIn,1,TX,,
10,2,0,knows,since,5,,,1997-01-01T12:00:00.000Z
12,2,3,knows,mval,3,,342.5,
12,2,3,knows,firstMetIn,1,TX,,
12,2,3,knows,since,5,,,2011-01-01T12:00:00.000Z
19,2,3,friendOf,strength,2,,4,
14,3,1,knows,mval,1,a,,
14,3,1,knows,firstMetIn,1,CA,,
14,3,1,knows,since,5,,,2010-01-01T12:00:00.000Z
15,3,2,knows,mval,1,z,,
15,3,2,knows,firstMetIn,1,CA,,
15,3,2,knows,since,5,,,2004-01-01T12:00:00.000Z
5,0,2,knows,mval,2,,23,
5,0,2,knows,firstMetIn,1,OH,,
5,0,2,knows,since,5,,,2002-01-01T12:00:00.000Z
6,0,3,knows,mval,3,,159.7,
6,0,3,knows,firstMetIn,1,IN,,
6,0,3,knows,since,5,,,1994-01-01T12:00:00.000Z
8,1,2,knows,mval,6,Y,,
8,1,2,knows,firstMetIn,1,FL,,
8,1,2,knows,since,5,,,1999-01-01T12:00:00.000Z
18,1,3,friendOf,strength,2,,5,
11,2,1,knows,mval,2,,1001,
11,2,1,knows,firstMetIn,1,OK,,
11,2,1,knows,since,5,,,2003-01-01T12:00:00.000Z
13,3,0,knows,mval,5,,,2001-01-01T12:00:00.000Z
13,3,0,knows,firstMetIn,1,CA,,
13,3,0,knows,since,5,,,2006-01-01T12:00:00.000Z
20,3,1,friendOf,strength,2,,3,
5.9.4.1 基本的な問合せの実行

2つの主要なJavaインタフェース、PgqlStatementPgqlResultSetがPGQLの実行に使用されます。このトピックには、基本的な問合せ実行のいくつかの例が含まれています。

例5-6 GraphLoaderExample.java

GraphLoaderExample.javaは、このトピックの後続の例で使用されるOracleプロパティ・グラフ・データをロードします。

import oracle.pg.rdbms.Oracle; 
import oracle.pg.rdbms.OraclePropertyGraph; 
import oracle.pg.rdbms.OraclePropertyGraphDataLoader;

/**
 * This example shows how to create an Oracle Property Graph 
 * and load data into it from vertex and edge flat files.
 */
public class GraphLoaderExample
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];
    String vertexFile         = args[idx++];
    String edgeFile           = args[idx++];

    Oracle oracle = null;
    OraclePropertyGraph opg = null;

    try {
      // Create a connection to Oracle
      oracle = new Oracle("jdbc:oracle:thin:@"+host+":"+port +":"+sid, user, password);

      // Create a property graph
      opg = OraclePropertyGraph.getInstance(oracle, graph);

      // Clear any existing data
      opg.clearRepository();

      // Load data from opv and ope files
      OraclePropertyGraphDataLoader opgLoader = OraclePropertyGraphDataLoader.getInstance();
      opgLoader.loadData(opg, vertexFile, edgeFile, 1);

      System.out.println("Vertices loaded:" + opg.countVertices());
      System.out.println("Edges loaded:" + opg.countEdges());

    }
    finally {
      // close the property graph
      if (opg != null) {
        opg.close();
      }
      // close oracle
      if (oracle != null) {
        oracle.dispose();
      }
    }
  }
}

GraphLoaderExample.javaは、次に示す test_graphの出力を生成します。

Vertices loaded:4
Edges loaded:17

例5-7 PgqlExample1.java

PgqlExample1.javaは、PGQL問合せを実行し、問合せ結果を出力します。PgqlConnectionは、PgqlStatementを取得するために使用されます。次に、PgqlStatementexecuteQueryメソッドをコールし、これにより、PgqlResultSetオブジェクトが返されます。PgqlResultSetには、表形式モードで結果を表示するprint()メソッドがあります。

PgqlResultSetオブジェクトとPgqlStatementオブジェクトは、問合せ結果の消費後にクローズする必要があります。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to execute a basic PGQL query against disk-resident 
 * PG data stored in Oracle Database and iterate through the result.
 */
public class PgqlExample1
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Execute query to get a PgqlResultSet object
      String pgql = 
        "SELECT v.\"fname\" AS fname, v.\"lname\" AS lname, v.\"mval\" AS mval "+
        "FROM MATCH (v)";
      rs = ps.executeQuery(pgql, /* query string */ 
                           ""    /* options */);

      // Print the results
      rs.print();
    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample1.javaは、test_graph (GraphLoaderExample.javaコードを使用してロード可能)に対して次の出力を提供します。

+---------------------------------------+
| FNAME | LNAME | MVAL                  |
+---------------------------------------+
| Susan | Blue  | false                 |
| Bill  | Brown | y                     |
| Ray   | Green | 1985-01-01 04:00:00.0 |
| John  | Black | 27                    |
+---------------------------------------+

例5-8 PgqlExample2.java

PgqlExample2.javaは、エッジ・プロパティに対するTemporalフィルタを持つPGQL問合せを示しています。

  • PgqlResultSetは、java.sql.ResultSetとよく似ている問合せ結果を消費するためのインタフェースを提供します。
  • next()メソッドを使用すると、問合せ結果内で移動できます。また、close()メソッドを使用すると、アプリケーションで問合せ結果の読込みが終了した後で、リソースを解放できます。
  • さらに、PgqlResultSetStringIntegerLongFloatDoubleBooleanLocalDateTimeOffsetDateTimeに対するgetterを提供し、任意の型の値に対する汎用のgetObject()メソッドを提供します。
import java.sql.Connection;

import java.text.SimpleDateFormat;

import java.util.Date;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.pgql.lang.ResultSet;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to execute a PGQL query with a temporal edge 
 * property filter against disk-resident PG data stored in Oracle Database 
 * and iterate through the result.
 */
public class PgqlExample2
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;
    ResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Create a Pgql connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Execute query to get a ResultSet object
      String pgql = 
        "SELECT v.\"fname\" AS n1, v2.\"fname\" AS n2, e.\"firstMetIn\" AS loc "+
        "FROM MATCH (v)-[e:\"knows\"]->(v2) "+
        "WHERE e.\"since\" > TIMESTAMP '2000-01-01 00:00:00.00+00:00'";
      rs = ps.executeQuery(pgql, "");

      // Print results
      printResults(rs);
    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }

  /**
   * Prints a PGQL ResultSet
   */
  static void printResults(ResultSet rs) throws Exception
  {
    StringBuffer buff = new StringBuffer("");
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
    while (rs.next()) {
      buff.append("[");
      for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
        // use generic getObject to handle all types
        Object mval = rs.getObject(i);
        String mStr = "";
        if (mval instanceof java.lang.String) {
          mStr = "STRING: "+mval.toString();
        }
        else if (mval instanceof java.lang.Integer) {
          mStr = "INTEGER: "+mval.toString();
        }
        else if (mval instanceof java.lang.Long) {
          mStr = "LONG: "+mval.toString();
        }
        else if (mval instanceof java.lang.Float) {
          mStr = "FLOAT: "+mval.toString();
        }
        else if (mval instanceof java.lang.Double) {
          mStr = "DOUBLE: "+mval.toString();
        }
        else if (mval instanceof java.sql.Timestamp) {
          mStr = "DATE: "+sdf.format((Date)mval);
        }
        else if (mval instanceof java.lang.Boolean) {
          mStr = "BOOLEAN: "+mval.toString();
        }
        if (i > 1) {
          buff.append(",\t");
        }
        buff.append(mStr);
      }
      buff.append("]\n");
    }
    System.out.println(buff.toString());
  }
}

PgqlExample2.javaは、test_graph (GraphLoaderExample.javaコードを使用してロード可能)に対して次の出力を提供します。

[STRING: Susan, STRING: Bill,   STRING: CA]
[STRING: Susan, STRING: John,   STRING: CA]
[STRING: Susan, STRING: Ray,    STRING: CA]
[STRING: Bill,  STRING: Ray,    STRING: OH]
[STRING: Ray,   STRING: John,   STRING: OK]
[STRING: Ray,   STRING: Susan,  STRING: TX]
[STRING: John,  STRING: Susan,  STRING: SC]
[STRING: John,  STRING: Bill,   STRING: GA]

例5-9 PgqlExample3.java

PgqlExample3.javaは、グループ化と集計を含むPGQL問合せを示しています。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to execute a PGQL query with aggregation 
 * against disk-resident PG data stored in Oracle Database and iterate 
 * through the result.
 */
public class PgqlExample3
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;
    PgqlResultSet rs = null;

    try {
      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Create a Pgql connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Execute query to get a ResultSet object
      String pgql = 
        "SELECT v.\"fname\" AS \"fname\", COUNT(v2) AS \"friendCnt\" "+
        "FROM MATCH (v)-[e:\"friendOf\"]->(v2) "+
        "GROUP BY v "+
        "ORDER BY \"friendCnt\" DESC";
      rs = ps.executeQuery(pgql, "");

      // Print results
      rs.print();
    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample3.javaは、test_graph (GraphLoaderExample.javaコードを使用してロード可能)に対して次の出力を提供します。

+-------------------+
| fname | friendCnt |
+-------------------+
| John  | 2         |
| Bill  | 1         |
| Ray   | 1         |
| Susan | 1         |
+-------------------+

例5-10 PgqlExample4.java

PgqlExample4.javaは、PGQLパス問合せを示しています。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to execute a path query in PGQL against 
 * disk-resident PG data stored in Oracle Database and iterate 
 * through the result.
 */
public class PgqlExample4
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Create a Pgql connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

  // Execute query to get a ResultSet object
      String pgql = 
        "PATH fof AS ()-[:\"friendOf\"|\"knows\"]->() "+
        "SELECT v2.\"fname\" AS friend "+
        "FROM MATCH (v)-/:fof*/->(v2) "+
        "WHERE v.\"fname\" = 'John' AND v != v2";
      rs = ps.executeQuery(pgql, "");

      // Print results
      rs.print();
    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample4.javaは、test_graph (GraphLoaderExample.javaコードを使用してロード可能)に対して次の出力を提供します。

+--------+
| FRIEND |
+--------+
| Susan  |
| Bill   |
| Ray    |
+--------+
5.9.4.2 PGQL問合せのセキュリティ手法

動的な問合せを実行するプログラムは、アプリケーションの整合性と機能を損なう可能性のあるインジェクション攻撃の影響を受ける可能性があります。

このトピックでは、文字列連結を使用してPGQL問合せを作成する際に、インジェクション攻撃を防ぐために使用できるいくつかの手法について説明します。

5.9.4.2.1 PGQL問合せでのバインド変数の使用

パフォーマンス改善とセキュリティ向上のためにPGQL問合せでバインド変数を使用できます。PGQL問合せの定数スカラー値は、バインド変数に置き換えることができます。バインド変数は、'?' (疑問符)で示されます。一定の年齢値よりも上の年齢の人を選択する、次の2つの問合せについて検討します。

// people older than 30
SELECT v.fname AS fname, v.lname AS lname, v.age AS age
FROM MATCH (v)
WHERE v.age > 30

// people older than 40
SELECT v.fname AS fname, v.lname AS lname, v.age AS age
FROM MATCH (v) 
WHERE v.age > 40

これらの問合せのSQL変換は、年齢フィルタに同様の方法で定数30および40を使用します。データベースは、これらの各問合せのハード解析を実行します。このハード解析時間は、単純な問合せの実行時間を超過することがよくあります。

次のように、各問合せ内の定数をバインド変数に置き換えることができます。

SELECT v.fname AS fname, v.lname AS lname, v.age AS age
FROM MATCH (v)
WHERE v.age > ?

これにより、SQLエンジンは、様々な年齢値に再利用できる、この問合せの汎用カーソルを作成できます。その結果、ハード解析が様々な年齢値に対してこの問合せを実行する必要がなくなり、各問合せの解析時間が大幅に短縮されます。

さらに、PGQL問合せでバインド変数を使用するアプリケーションでは、文字列連結を使用して定数値をPGQL問合せに埋め込むものに比べ、インジェクション攻撃に対する脆弱性が軽減されます。

カーソル共有とバインド変数の詳細は、『Oracle Database SQLチューニング・ガイド』も参照してください。

PgqlExample5.javaに示すように、PgqlPreparedStatementインタフェースは、バインド変数を使用する問合せを実行するために使用できます。PgqlPreparedStatementには、問合せ実行のための値の設定に使用できる様々な値タイプ用の複数の設定メソッドが用意されています。

PGQLではバインド変数にいくつかの制限があります。バインド変数は、定数プロパティ値にのみを使用できます。つまり、頂点とエッジをバインド変数に置き換えることはできません。また、一旦特定のバインド変数をある型に設定したら、それを別の型に設定することはできません。たとえば、PgqlPreparedStatementに対してsetInt(1, 30)が実行される場合、その同じPgqlPreparedStatementsetString(1, "abc")をコールすることはできません。

例5-11 PgqlExample5.java

PgqlExample5.javaは、PGQL問合せでのバインド変数の使用方法を示しています。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlPreparedStatement;
import oracle.pg.rdbms.pgql.PgqlResultSet;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to use bind variables with a PGQL query.
 */
public class PgqlExample5
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlPreparedStatement pps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Create a Pgql connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Query string with a bind variable (denoted by ?)
      String pgql = 
        "SELECT v.\"fname\" AS fname, v.\"lname\" AS lname, v.\"age\" AS age "+
        "FROM MATCH (v) "+
        "WHERE v.\"age\" > ?";

      // Create a PgqlPreparedStatement
      pps = pgqlConn.prepareStatement(pgql);

      // Set filter value to 30
      pps.setInt(1, 30);

      // execute query
      rs = pps.executeQuery();

      // Print query results
      System.out.println("-- Values for v.\"age\" > 30 --");
      rs.print();
      // close result set
      rs.close();

      // set filter value to 40
      pps.setInt(1, 40);

      // execute query
      rs = pps.executeQuery();

      // Print query results
      System.out.println("-- Values for v.\"age\" > 40 --");
      rs.print();
      // close result set
      rs.close();
    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (pps != null) {
        pps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample5.javaには、test_graph (GraphLoaderExample.javaコードを使用してロード可能)に対する次の出力があります。

-- Values for v.age > 30 --
+---------------------+
| fname | lname | age |
+---------------------+
| Susan | Blue  | 35  |
| Bill  | Brown | 40  |
| Ray   | Green | 41  |
+---------------------+
-- Values for v.age > 40 --
+---------------------+
| fname | lname | age |
+---------------------+
| Ray   | Green | 41  |
+---------------------+

例5-12 PgqlExample6.java

PgqlExample6.javaは、2つのバインド変数、1つのString変数と1つのTimestamp変数を含む問合せを示しています。

import java.sql.Connection;
import java.sql.Timestamp;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlPreparedStatement;
import oracle.pg.rdbms.pgql.PgqlResultSet;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to use multiple bind variables with a PGQL query.
 */
public class PgqlExample6
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlPreparedStatement pps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Create a Pgql connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Query string with multiple bind variables
      String pgql = 
        "SELECT v1.\"fname\" AS fname1, v2.\"fname\" AS fname2 "+
        "FROM MATCH (v1)-[e:\"knows\"]->(v2) "+
        "WHERE e.\"since\" < ? AND e.\"firstMetIn\" = ?";

      // Create a PgqlPreparedStatement
      pps = pgqlConn.prepareStatement(pgql);

      // Set e.since < 2006-01-01T12:00:00.00Z
      Timestamp t = Timestamp.valueOf(OffsetDateTime.parse("2006-01-01T12:00:01.00Z").atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime());
      pps.setTimestamp(1, t);
      // Set e.firstMetIn = 'CA'
      pps.setString(2, "CA");

      // execute query
      rs = pps.executeQuery();

      // Print query results
      System.out.println("-- Values for e.\"since\" <  2006-01-01T12:00:01.00Z AND e.\"firstMetIn\" = 'CA' --");
      rs.print();
      // close result set
      rs.close();

      // Set e.since < 2000-01-01T12:00:00.00Z
      t = Timestamp.valueOf(OffsetDateTime.parse("2000-01-01T12:00:00.00Z").atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime());
      pps.setTimestamp(1, t);
      // Set e.firstMetIn = 'TX'
      pps.setString(2, "TX");

      // execute query
      rs = pps.executeQuery();

      // Print query results
      System.out.println("-- Values for e.\"since\" <  2000-01-01T12:00:00.00Z AND e.\"firstMetIn\" = 'TX' --");
      rs.print();
      // close result set
      rs.close();
    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (pps != null) {
        pps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample6.javaは、test_graph (GraphLoaderExample.javaコードを使用してロード可能)に対して次の出力を提供します。

-- Values for e."since" <  2006-01-01T12:00:01.00Z AND e."firstMetIn" = 'CA' --
+-----------------+
| FNAME1 | FNAME2 |
+-----------------+
| Susan  | Bill   |
| Susan  | Ray    |
+-----------------+
-- Values for e."since" <  2000-01-01T12:00:00.00Z AND e."firstMetIn" = 'TX' --
+-----------------+
| FNAME1 | FNAME2 |
+-----------------+
| Ray    | Bill   |
+-----------------+
5.9.4.2.2 PGQL識別子の検証

PGQL問合せの一部では、パーサーはバインド変数を使用できません。そのような場合は、パッケージoracle.pgql.lang.ir.PgqlUtilsprintIdentifierメソッドを使用して入力を検証できます。

グラフ・パターンが照合されるグラフを連結する、次の問合せ実行について考えてみます。

stmt.executeQuery("SELECT n.name FROM MATCH (n) ON " + graphName, "");

インジェクションを回避するために、識別子graphNameを次に示すように検証する必要があります。

stmt.executeQuery("SELECT n.name FROM MATCH (n) ON " + PgqlUtils.printIdentifier(graphName), "");
5.9.4.3 PGQL問合せでのテキスト索引の使用

Oracle Databaseに対して実行されたPGQL問合せは、頂点プロパティとエッジ・プロパティ用に作成されたOracle Text索引を使用できます。テキスト索引を作成した後は、CONTAINS演算子を使用して全文検索を実行できます。CONTAINSには、頂点プロパティまたはエッジ・プロパティとOracle Text検索文字列という2つの引数があります。ワイルドカード、Stemming、およびSoundexなどの高度な機能を含め、すべての有効なOracle Text検索文字列を使用できます。

例5-13 PgqlExample7.java

PgqlExample7.javaは、CONTAINS問合せの実行方法を示しています。

import java.sql.CallableStatement;
import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to use an Oracle Text index with a PGQL query.
 */
public class PgqlExample7
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Create text index with SQL API
      CallableStatement cs = null;
      // text index on vertices
      cs = conn.prepareCall(
        "begin opg_apis.create_vertices_text_idx(:1,:2); end;"
      );
      cs.setString(1,user);
      cs.setString(2,graph);
      cs.execute();
      cs.close();
      // text index on edges
      cs = conn.prepareCall(
        "begin opg_apis.create_edges_text_idx(:1,:2); end;"
      );
      cs.setString(1,user);
      cs.setString(2,graph);
      cs.execute();
      cs.close(); 

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Query using CONTAINS text search operator on vertex property
      // Find all vertices with an lname property value that starts with 'B'
      String pgql = 
        "SELECT v.\"fname\" AS fname, v.\"lname\" AS lname "+
        "FROM MATCH (v) "+
        "WHERE CONTAINS(v.\"lname\",'B%')";

      // execute query
      rs = ps.executeQuery(pgql, "");

      // print results
      System.out.println("-- Vertex Property Query --");
      rs.print();

      // close result set
      rs.close();

      // Query using CONTAINS text search operator on edge property
      // Find all knows edges with a firstMetIn property value that ends with 'A'
      pgql = 
        "SELECT v1.\"fname\" AS fname1, v2.\"fname\" AS fname2, e.\"firstMetIn\" AS loc "+
        "FROM MATCH (v1)-[e:\"knows\"]->(v2) "+
        "WHERE CONTAINS(e.\"firstMetIn\",'%A')";

      // execute query
      rs = ps.executeQuery(pgql, "");

      // print results
      System.out.println("-- Edge Property Query --");
      rs.print();

    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample7.javaには、test_graph (GraphLoaderExample.javaコードを使用してロード可能)に対する次の出力があります。

-- Vertex Property Query --
+---------------+
| FNAME | LNAME |
+---------------+
| Susan | Blue  |
| Bill  | Brown |
| John  | Black |
+---------------+
-- Edge Property Query --
+-----------------------+
| FNAME1 | FNAME1 | LOC |
+-----------------------+
| Susan  | Bill   | CA  |
| John   | Bill   | GA  |
| Susan  | John   | CA  |
| Susan  | Ray    | CA  |
+-----------------------+
5.9.4.4 PGQL問合せのSQL変換の取得

PgqlStatementメソッドとPgqlPreparedStatementメソッドを介してPGQL問合せのSQL変換を取得できます。PGQL問合せの未加工のSQLは、次のいくつかの理由で役立ちます。

  • 他のSQLベースのツールまたはインタフェース(たとえば、SQL*PlusやSQL Developer)を使用してデータベースに対してSQLを直接実行できます。

  • 生成されたSQLをカスタマイズおよびチューニングして、パフォーマンスの最適化やアプリケーションの特定要件の達成を実行できます。

  • PGQL副問合せを、Oracle Databaseに格納されている他のデータ(リレーショナル表、空間データ、およびJSONデータなど)と結合する大規模なSQL問合せを作成できます。

例5-14 PgqlExample8.java

PgqlExample8.javaは、PGQL問合せの未加工のSQL変換の取得方法を示しています。PgqlStatementtranslateQueryメソッドは、問合せからの戻り列とSQL変換自体に関する情報を含むPgqlSqlQueryTransオブジェクトを返します。

変換されたSQLは、PGQL問合せから射影される「論理」オブジェクトまたは値の型に応じて異なる列を返します。PGQLに射影される頂点またはエッジには、変換されたSQLに射影される、2つの対応する列があります。

  • $IT : ID型– NVARCHAR(1): 「V」は頂点、「E」はエッジ。

  • $ID :頂点IDまたはエッジID – NUMBER: VT$表とGE$表内のVID列またはEID例と同じ内容

PGQLに射影されているプロパティ値または定数スカラー値には、変換されたSQLに射影される4つの対応する列があります。

  • $T :値の型– NUMBER: VT$表とGE$表内のT列と同じ内容

  • $V:値– NVARCHAR2(15000): VT$表とGE$表内のV列と同じ内容

  • $VN:数値の値– NUMBER: VT$表とGE$表内のVN列と同じ内容

  • $VT: Temporal値– TIMESTAMP WITH TIME ZONE: VT$表とGE$表内のVT列と同じ内容

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlColumnDescriptor;
import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlStatement;
import oracle.pg.rdbms.pgql.PgqlSqlQueryTrans;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to obtain the SQL translation for a PGQL query.
 */
public class PgqlExample8
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Create a Pgql connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // PGQL query to be translated
      String pgql = 
        "SELECT v1, v1.\"fname\" AS fname1, e, e.\"since\" AS since "+
        "FROM MATCH (v1)-[e:\"knows\"]->(v2)";

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Get the SQL translation
      PgqlSqlQueryTrans sqlTrans = ps.translateQuery(pgql,"");

      // Get the return column descriptions
      PgqlColumnDescriptor[] cols = sqlTrans.getReturnTypes();

      // Print column descriptions
      System.out.println("-- Return Columns -----------------------");
      printReturnCols(cols);

      // Print SQL translation
      System.out.println("-- SQL Translation ----------------------");
      System.out.println(sqlTrans.getSqlTranslation());
    }
    finally {
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }

  /**
   * Prints return columns for a SQL translation
   */
  static void printReturnCols(PgqlColumnDescriptor[] cols) throws Exception
  {
    StringBuffer buff = new StringBuffer("");

    for (int i = 0; i < cols.length; i++) {

      String colName = cols[i].getColName();
      PgqlColumnDescriptor.Type colType = cols[i].getColType();
      int offset = cols[i].getSqlOffset();

      String readableType = "";
      switch(colType) {
        case VERTEX:
          readableType = "VERTEX";
          break;
        case EDGE:
          readableType = "EDGE";
          break;
        case VALUE:
          readableType = "VALUE";
          break;
      }

      buff.append("colName=["+colName+"] colType=["+readableType+"] offset=["+offset+"]\n");
    }
    System.out.println(buff.toString());
  }
}

PgqlExample8.javaには、test_graph (GraphLoaderExample.javaコードを使用してロード可能)に対する次の出力があります。

-- Return Columns -----------------------
colName=[v1] colType=[VERTEX] offset=[1]
colName=[fname1] colType=[VALUE] offset=[3]
colName=[e] colType=[EDGE] offset=[7]
colName=[since] colType=[VALUE] offset=[9]
-- SQL Translation ----------------------
SELECT n'V' AS "V1$IT",
T0$0.SVID AS "V1$ID",
T0$1.T AS "FNAME1$T",
T0$1.V AS "FNAME1$V",
T0$1.VN AS "FNAME1$VN",
T0$1.VT AS "FNAME1$VT",
n'E' AS "E$IT",
T0$0.EID AS "E$ID",
T0$0.T AS "SINCE$T",
T0$0.V AS "SINCE$V",
T0$0.VN AS "SINCE$VN",
T0$0.VT AS "SINCE$VT"
FROM ( SELECT L.EID, L.SVID, L.DVID, L.EL, R.K, R.T, R.V, R.VN, R.VT
  FROM "SCOTT".TEST_GRAPHGT$ L,
       (SELECT * FROM "SCOTT".TEST_GRAPHGE$ WHERE K=n'since' ) R
  WHERE L.EID = R.EID(+)
) T0$0,
( SELECT L.VID, L.VL, R.K, R.T, R.V, R.VN, R.VT
  FROM "SCOTT".TEST_GRAPHVD$ L,
       (SELECT * FROM "SCOTT".TEST_GRAPHVT$ WHERE K=n'fname' ) R
  WHERE L.VID = R.VID(+)
) T0$1
WHERE T0$0.SVID=T0$1.VID AND
(T0$0.EL = n'knows' AND T0$0.EL IS NOT NULL)

例5-15 PgqlExample9.java

また、バインド変数を含むPGQL問合せのSQL変換も取得できます。この場合、対応するSQL変換にもバインド変数が含まれます。PgqlSqlQueryTransインタフェースには、SQL問合せにバインドされている必要があるJavaオブジェクトの順序付きリスト(リストの最初のオブジェクトは1の位置に設定され、2番目は2の位置に設定されている必要がある)を返すgetSqlBvListメソッドがあります。

PgqlExample9.javaは、バインド変数を使用したPGQL問合せのSQLの取得および実行方法を示しています。

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Timestamp;

import java.util.List;

import oracle.pg.rdbms.pgql.PgqlColumnDescriptor;
import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlPreparedStatement;
import oracle.pg.rdbms.pgql.PgqlSqlQueryTrans;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to obtain and execute the SQL translation for a 
 * PGQL query that uses bind variables.
 */
public class PgqlExample9
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlPreparedStatement pgqlPs = null;

    PreparedStatement sqlPs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Create a Pgql connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Execute query to get a ResultSet object
      String pgql = 
        "SELECT v1, v1.\"fname\" AS fname1, v1.\"age\" AS age, ? as constVal "+
        "FROM MATCH (v1) "+
        "WHERE  v1.\"fname\" = ? OR v1.\"age\" < ?";


      // Create a PgqlStatement
      pgqlPs = pgqlConn.prepareStatement(pgql);

      // set bind values
      pgqlPs.setDouble(1, 2.05d);
      pgqlPs.setString(2, "Bill");
      pgqlPs.setInt(3, 35);

      // Get the SQL translation
      PgqlSqlQueryTrans sqlTrans = pgqlPs.translateQuery("");

      // Get the SQL String
      String sqlStr = sqlTrans.getSqlTranslation();

      // Get the return column descriptions
      PgqlColumnDescriptor[] cols = sqlTrans.getReturnTypes();

      // Get the bind values
      List<Object> bindVals = sqlTrans.getSqlBvList();

      // Print column descriptions
      System.out.println("-- Return Columns -----------------------");
      printReturnCols(cols);

      // Print SQL translation
      System.out.println("-- SQL Translation ----------------------");
      System.out.println(sqlStr);

      // Print Bind Values
      System.out.println("\n-- Bind Values --------------------------");
      for (Object obj : bindVals) {
        System.out.println(obj.toString());
      }

      // Execute Query
      // Get PreparedStatement
      sqlPs = conn.prepareStatement("SELECT COUNT(*) FROM ("+sqlStr+")");
      // Set bind values and execute the PreparedStatement
      executePs(sqlPs, bindVals);

      // Set new bind values in the PGQL PreparedStatement 
      pgqlPs.setDouble(1, 3.02d);
      pgqlPs.setString(2, "Ray");
      pgqlPs.setInt(3, 30);

      // Print Bind Values
      bindVals = sqlTrans.getSqlBvList();
      System.out.println("\n-- Bind Values --------------------------");
      for (Object obj : bindVals) {
        System.out.println(obj.toString());
      }

      // Execute the PreparedStatement with new bind values
      executePs(sqlPs, bindVals);
    }
    finally {
      // close the SQL statement
      if (sqlPs != null) {
        sqlPs.close();
      }
      // close the statement
      if (pgqlPs != null) {
        pgqlPs.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }

  /**
   * Executes a SQL PreparedStatement with the input bind values
   */
  static void executePs(PreparedStatement ps, List<Object> bindVals) throws Exception
  {
    ResultSet rs = null;
    try {
      // Set bind values
      for (int idx = 0; idx < bindVals.size(); idx++) {
        Object o = bindVals.get(idx);
        // String
        if (o instanceof java.lang.String) {
          ps.setNString(idx + 1, (String)o);
        }
        // Int
        else if (o instanceof java.lang.Integer) {
          ps.setInt(idx + 1, ((Integer)o).intValue());
        }
        // Long
        else if (o instanceof java.lang.Long) {
          ps.setLong(idx + 1, ((Long)o).longValue());
        }
        // Float
        else if (o instanceof java.lang.Float) {
          ps.setFloat(idx + 1, ((Float)o).floatValue());
        }
        // Double
        else if (o instanceof java.lang.Double) {
          ps.setDouble(idx + 1, ((Double)o).doubleValue());
        }
       // Timestamp
       else if (o instanceof java.sql.Timestamp) {
         ps.setTimestamp(idx + 1, (Timestamp)o);
       }
       else {
         ps.setString(idx + 1, bindVals.get(idx).toString());
       }
     }

      // Execute query
      rs = ps.executeQuery();
      if (rs.next()) {
        System.out.println("\n-- Execute Query: Result has "+rs.getInt(1)+" rows --");
      }
    }
    finally {
      // close the SQL ResultSet
      if (rs != null) {
        rs.close();
      }
    }
  }

  /**
   * Prints return columns for a SQL translation
   */
  static void printReturnCols(PgqlColumnDescriptor[] cols) throws Exception
  {
    StringBuffer buff = new StringBuffer("");

    for (int i = 0; i < cols.length; i++) {

      String colName = cols[i].getColName();
      PgqlColumnDescriptor.Type colType = cols[i].getColType();
      int offset = cols[i].getSqlOffset();

      String readableType = "";
      switch(colType) {
        case VERTEX:
          readableType = "VERTEX";
          break;
        case EDGE:
          readableType = "EDGE";
          break;
        case VALUE:
          readableType = "VALUE";
          break;
      }

      buff.append("colName=["+colName+"] colType=["+readableType+"] offset=["+offset+"]\n");
    }
    System.out.println(buff.toString());
  }
}

PgqlExample9.javaには、test_graph (GraphLoaderExample.javaコードを使用してロード可能)に対する次の出力があります。

–-- Return Columns -----------------------
colName=[v1] colType=[VERTEX] offset=[1]
colName=[fname1] colType=[VALUE] offset=[3]
colName=[age] colType=[VALUE] offset=[7]
colName=[constVal] colType=[VALUE] offset=[11]
-- SQL Translation ----------------------
SELECT n'V' AS "V1$IT",
T0$0.VID AS "V1$ID",
T0$0.T AS "FNAME1$T",
T0$0.V AS "FNAME1$V",
T0$0.VN AS "FNAME1$VN",
T0$0.VT AS "FNAME1$VT",
T0$1.T AS "AGE$T",
T0$1.V AS "AGE$V",
T0$1.VN AS "AGE$VN",
T0$1.VT AS "AGE$VT",
4 AS "CONSTVAL$T",
to_nchar(?,'TM9','NLS_Numeric_Characters=''.,''') AS "CONSTVAL$V",
? AS "CONSTVAL$VN",
to_timestamp_tz(null) AS "CONSTVAL$VT"
FROM ( SELECT L.VID, L.VL, R.K, R.T, R.V, R.VN, R.VT
  FROM "SCOTT".TEST_GRAPHVD$ L,
       (SELECT * FROM "SCOTT".TEST_GRAPHVT$ WHERE K=n'fname' ) R
  WHERE L.VID = R.VID(+)
) T0$0,
( SELECT L.VID, L.VL, R.K, R.T, R.V, R.VN, R.VT
  FROM "SCOTT".TEST_GRAPHVD$ L,
       (SELECT * FROM "SCOTT".TEST_GRAPHVT$ WHERE K=n'age' ) R
  WHERE L.VID = R.VID(+)
) T0$1
WHERE T0$0.VID=T0$1.VID AND
((T0$0.T = 1 AND T0$0.V = ?) OR T0$1.VN < ?)

-- Bind Values --------------------------
2.05
2.05
Bill
35
-- Execute Query: Result has 2 rows --

-- Bind Values --------------------------
3.02
3.02
Ray
30
-- Execute Query: Result has 1 rows --
5.9.4.5 PGQL変換および実行の追加オプション

PGQL問合せの変換および実行に影響を与える複数のオプションがあります。問合せオプションを設定する主な方法は次のとおりです。

  • executeQueryおよびtranslateQueryに対して明示的引数を使用する

  • executeQueryおよびtranslateQueryoptions文字列引数でフラグを使用する

  • Java JVM引数を使用する

次の表に、PGQLの変換と実行に使用できる問合せ引数をまとめています。

表5-2 PGQLの変換および実行のオプション

オプション デフォルト 明示的引数 オプション・フラグ JVM引数

並列度

0

parallel

なし

なし

タイムアウト

unlimited

timeout

なし

なし

動的サンプリング

2

dynamicSampling

なし

なし

結果の最大数

unlimited

maxResults

なし

なし

GT$表の使用

on

なし

USE_GT_TAB=F

-Doracle.pg.rdbms.pgql.useGtTab=false

CONNECT BYの使用

off

なし

USE_RW=F

-Doracle.pg.rdbms.pgql.useRW=false

明確な再帰的WITHの使用

off

なし

USE_DIST_RW=T

-Doracle.pg.rdbms.pgql.useDistRW=true

パスの最大長

unlimited

なし

MAX_PATH_LEN=n

-Doracle.pg.rdbms.pgql.maxPathLen=n

partialの設定

false

なし

EDGE_SET_PARTIAL=T

-Doracle.pg.rdbms.pgql.edgeSetPartial=true

プロジェクトのNullプロパティ

true

なし

PROJ_NULL_PROPS=F

-Doracle.pg.rdbms.pgql.projNullProps=false

VT$ VL列使用

on

なし

USE_VL_COL=F

-Doracle.pg.rdbms.pgql.useVLCol=false

5.9.4.5.1 明示的引数によって制御される問合せオプション

一部の問合せオプションは、Java APIのメソッドに対する明示的引数によって制御されます。

  • PgqlStatementexecuteQueryメソッドには、タイムアウト(秒単位)、並列度、オプティマイザ動的サンプリング、および最大結果数の明示的引数があります。

  • translateQueryメソッドには、並列性、オプティマイザ動的サンプリング、および最大結果数の明示的引数があります。また、PgqlPreparedStatementには、executeQueryおよびtranslateQuery用にこれらの同じ追加引数が用意されています。

例5-16 PgqlExample10.java

PgqlExample10.javaは、明示的引数によって制御される追加オプションを指定したPGQL問合せの実行を示しています。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to execute a PGQL query with various options.
 */
public class PgqlExample10
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Execute query to get a ResultSet object
      String pgql = 
        "SELECT v1.\"fname\" AS fname1, v2.\"fname\" AS fname2 "+
        "FROM MATCH (v1)-[:\"friendOf\"]->(v2)";
      rs = ps.executeQuery(pgql /* query string */, 
                           100  /* timeout (sec): 0 is default and implies no timeout */,
                           2    /* parallel: 1 is default */,
                           6    /* dynamic sampling: 2 is default */,
                           50   /* max results: -1 is default and implies no limit */,
                           ""   /* options */);

      // Print query results
      rs.print();
    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample10.javaは、test_graph (GraphLoaderExample.javaコードを使用してロード可能)に対して次の出力を提供します。

+-----------------+
| FNAME1 | FNAME2 |
+-----------------+
| Ray    | Susan  |
| John   | Susan  |
| Bill   | John   |
| Susan  | John   |
| John   | Bill   |
+-----------------+
5.9.4.5.2 GT$スケルトン表の使用

プロパティ・グラフ・リレーショナル・スキーマは、エッジが持っているプロパティの数に関係なく、グラフ内のエッジごとに1行を格納するGT$スケルトン表を定義します。デフォルトで、このスケルトン表にはデータは移入されているため、PGQL問合せ実行は、多くの場合、GT$表を利用して、GE$表でのソート操作を回避できます。それにより、大幅にパフォーマンスが改善されます。

executeQueryおよびtranslateQueryoptions引数に"USE_GT_TAB=F"を追加するか、Javaコマンドラインで-Doracle.pg.rdbms.pgql.useGtTab=falseを使用してGT$表の使用をオフにできます。

例5-17 PgqlExample11.java

PgqlExample11.javaは、GT$スケルトン表を使用する問合せを示します。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlSqlQueryTrans;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to avoid using the GT$ skeleton table for
 * PGQL query execution.
 */
public class PgqlExample11
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Execute query to get a ResultSet object
      String pgql = 
        "SELECT id(v1), id(v2) "+
        "FROM MATCH (v1)-[knows]->(v2)";

      // Get the SQL translation with GT table
      PgqlSqlQueryTrans sqlTrans = ps.translateQuery(pgql,"");

      // Print SQL translation
      System.out.println("-- SQL Translation with GT Table ----------------------");
      System.out.println(sqlTrans.getSqlTranslation());

      // Get the SQL translation without GT table
      sqlTrans = ps.translateQuery(pgql,"USE_GT_TAB=F");

      // Print SQL translation
      System.out.println("-- SQL Translation without GT Table -------------------------");
      System.out.println(sqlTrans.getSqlTranslation());

    }
    finally {
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample11.javaは、test_graph (GraphLoaderExample.javaコードを使用してロード可能)に対して次の出力を提供します。

-- SQL Translation with GT Table ----------------------
SELECT 7 AS "id(v1)$T",
to_nchar(T0$0.SVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v1)$V",
T0$0.SVID AS "id(v1)$VN",
to_timestamp_tz(null) AS "id(v1)$VT",
7 AS "id(v2)$T",
to_nchar(T0$0.DVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v2)$V",
T0$0.DVID AS "id(v2)$VN",
to_timestamp_tz(null) AS "id(v2)$VT"
FROM "SCOTT".TEST_GRAPHGT$ T0$0
-- SQL Translation without GT Table -------------------------
SELECT 7 AS "id(v1)$T",
to_nchar(T0$0.SVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v1)$V",
T0$0.SVID AS "id(v1)$VN",
to_timestamp_tz(null) AS "id(v1)$VT",
7 AS "id(v2)$T",
to_nchar(T0$0.DVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v2)$V",
T0$0.DVID AS "id(v2)$VN",
to_timestamp_tz(null) AS "id(v2)$VT"
FROM (SELECT DISTINCT EID, SVID, DVID,EL FROM "SCOTT".TEST_GRAPHGE$) T0$0
5.9.4.5.3 パス問合せのオプション

PGQLでパス問合せを実行するためのオプションがいくつかあります。Oracle SQLには、CONNECT BY句または再帰的WITH句という2つの基本的評価メソッドがあります。再帰的WITHがデフォルトの評価メソッドになります。さらに、再帰的WITH評価メソッドをさらに変更して、問合せ評価の再帰的ステップ中にDISTINCT修飾子を含めることができます。各ステップで個別の頂点を計算することにより、連結度が高いグラフでの組合せの激増を防止できます。DISTINCT修飾子は、データベース内に特定のパラメータ("_recursive_with_control"=8)を設定する必要があるため、デフォルトでは追加されません。

検索するパスの最大長も制御できます。この場合のパスの長さは、*演算子と+演算子を評価する際に許可される反復数として定義されます。デフォルトの最大長は無制限です。

パスの評価オプションを要約すると次のようになります。

  • CONNECT BY: CONNECT BY句を使用するには、options引数に'USE_RW=F'を指定するか、Javaコマンドラインに-Doracle.pg.rdbms.pgql.useRW=falseを指定します。

  • 再帰的WITHでのDistinct修飾子:再帰的ステップでDISTINCT修飾子を使用するには、まずデータベース・セッションで"_recursive_with_control"=8を設定してから、options引数に'USE_DIST_RW=T'を指定するか、Javaコマンドラインに -Doracle.pg.rdbms.pgql.useDistRW=trueを指定します。セッションで"_recursive_with_control"が8に設定されていない場合、「ORA-32486:再帰的WITH句の再帰的ブランチでのサポートされていない操作です」が発生します。

  • パス長の制限:nに対する*および+を評価する際の最大反復数を制限するには、問合せのoptions引数に'MAX_PATH_LEN=n'を指定するか、Javaコマンドラインに-Doracle.pg.rdbms.pgql.maxPathLen=nを指定します。

例5-18 PgqlExample12.java

PgqlExample12.javaは、様々なオプションでのパス問合せの変換を示しています。

import java.sql.Connection;
import java.sql.Statement;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlSqlQueryTrans;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to use various options with PGQL path queries.
 */
public class PgqlExample12
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Set "_recursive_with_control"=8 to enable distinct optimization
      // optimization for recursive with
      Statement stmt = conn.createStatement();
      stmt.executeUpdate("alter session set \"_recursive_with_control\"=8");
      stmt.close();

      // Path Query to illustrate options      
      String pgql = 
        "PATH fof AS ()-[:\"friendOf\"]->() "+
        "SELECT id(v1), id(v2) "+
        "FROM MATCH (v1)-/:fof*/->(v2) "+
        "WHERE id(v1) = 2";


      // get SQL translation with defaults - Non-distinct Recursive WITH
      PgqlSqlQueryTrans sqlTrans = 
        ps.translateQuery(pgql /* query string */, 
                          2    /* parallel: default is 1 */,
                          2    /* dynamic sampling: default is 2 */,
                          -1   /* max results: -1 implies no limit */,
                          ""   /* options */);
      System.out.println("-- Default Path Translation --------------------");
      System.out.println(sqlTrans.getSqlTranslation()+"\n");

      // get SQL translation with DISTINCT reachability optimization
      sqlTrans = 
        ps.translateQuery(pgql /* query string */, 
                          2    /* parallel: default is 1 */,
                          2    /* dynamic sampling: default is 2 */,
                          -1   /* max results: -1 implies no limit */,
                          " USE_DIST_RW=T " /* options */);
      System.out.println("-- DISTINCT RW Path Translation --------------------");
      System.out.println(sqlTrans.getSqlTranslation()+"\n");

      // get SQL translation with CONNECT BY
      sqlTrans = 
        ps.translateQuery(pgql /* query string */, 
                          2    /* parallel: default is 1 */,
                          2    /* dynamic sampling: default is 2 */,
                          -1   /* max results: -1 implies no limit */,
                          " USE_RW=F " /* options */);
      System.out.println("-- CONNECT BY Path Translation --------------------");
      System.out.println(sqlTrans.getSqlTranslation()+"\n");
    }
    finally {
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample12.javaは、test_graph (GraphLoaderExample.javaコードを使用してロード可能)に対して次の出力を提供します。

-- Default Path Translation --------------------
SELECT /*+ parallel(2) */ * FROM(SELECT 7 AS "id(v1)$T",
to_nchar(T0$0.SVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v1)$V",
T0$0.SVID AS "id(v1)$VN",
to_timestamp_tz(null) AS "id(v1)$VT",
7 AS "id(v2)$T",
to_nchar(T0$0.DVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v2)$V",
T0$0.DVID AS "id(v2)$VN",
to_timestamp_tz(null) AS "id(v2)$VT"
FROM (/*Path[*/SELECT DISTINCT SVID, DVID
FROM (
SELECT 2 AS SVID, 2 AS DVID
FROM SYS.DUAL
WHERE EXISTS(
SELECT 1
FROM "SCOTT".TEST_GRAPHVT$
WHERE VID = 2)
UNION ALL
SELECT SVID,DVID FROM
(WITH RW (ROOT, DVID) AS
( SELECT ROOT, DVID FROM
(SELECT SVID ROOT, DVID
FROM (SELECT T0$0.SVID AS SVID,
T0$0.DVID AS DVID
FROM "SCOTT".TEST_GRAPHGT$ T0$0
WHERE T0$0.SVID = 2 AND
(T0$0.EL = n'friendOf' AND T0$0.EL IS NOT NULL))
) UNION ALL
SELECT RW.ROOT, R.DVID
FROM (SELECT T0$0.SVID AS SVID,
T0$0.DVID AS DVID
FROM "SCOTT".TEST_GRAPHGT$ T0$0
WHERE (T0$0.EL = n'friendOf' AND T0$0.EL IS NOT NULL)) R, RW
WHERE RW.DVID = R.SVID )
CYCLE DVID SET cycle_col TO 1 DEFAULT 0
SELECT ROOT SVID, DVID FROM RW))/*]Path*/) T0$0
WHERE T0$0.SVID = 2)

-- DISTINCT RW Path Translation --------------------
SELECT /*+ parallel(2) */ * FROM(SELECT 7 AS "id(v1)$T",
to_nchar(T0$0.SVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v1)$V",
T0$0.SVID AS "id(v1)$VN",
to_timestamp_tz(null) AS "id(v1)$VT",
7 AS "id(v2)$T",
to_nchar(T0$0.DVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v2)$V",
T0$0.DVID AS "id(v2)$VN",
to_timestamp_tz(null) AS "id(v2)$VT"
FROM (/*Path[*/SELECT DISTINCT SVID, DVID
FROM (
SELECT 2 AS SVID, 2 AS DVID
FROM SYS.DUAL
WHERE EXISTS(
SELECT 1
FROM "SCOTT".TEST_GRAPHVT$
WHERE VID = 2)
UNION ALL
SELECT SVID,DVID FROM
(WITH RW (ROOT, DVID) AS
( SELECT ROOT, DVID FROM
(SELECT SVID ROOT, DVID
FROM (SELECT T0$0.SVID AS SVID,
T0$0.DVID AS DVID
FROM "SCOTT".TEST_GRAPHGT$ T0$0
WHERE T0$0.SVID = 2 AND
(T0$0.EL = n'friendOf' AND T0$0.EL IS NOT NULL))
) UNION ALL
SELECT DISTINCT RW.ROOT, R.DVID
FROM (SELECT T0$0.SVID AS SVID,
T0$0.DVID AS DVID
FROM "SCOTT".TEST_GRAPHGT$ T0$0
WHERE (T0$0.EL = n'friendOf' AND T0$0.EL IS NOT NULL)) R, RW
WHERE RW.DVID = R.SVID )
CYCLE DVID SET cycle_col TO 1 DEFAULT 0
SELECT ROOT SVID, DVID FROM RW))/*]Path*/) T0$0
WHERE T0$0.SVID = 2)

-- CONNECT BY Path Translation --------------------
SELECT /*+ parallel(2) */ * FROM(SELECT 7 AS "id(v1)$T",
to_nchar(T0$0.SVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v1)$V",
T0$0.SVID AS "id(v1)$VN",
to_timestamp_tz(null) AS "id(v1)$VT",
7 AS "id(v2)$T",
to_nchar(T0$0.DVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v2)$V",
T0$0.DVID AS "id(v2)$VN",
to_timestamp_tz(null) AS "id(v2)$VT"
FROM (/*Path[*/SELECT DISTINCT SVID, DVID
FROM (
SELECT 2 AS SVID, 2 AS DVID
FROM SYS.DUAL
WHERE EXISTS(
SELECT 1
FROM "SCOTT".TEST_GRAPHVT$
WHERE VID = 2)
UNION ALL
SELECT SVID, DVID
FROM
(SELECT CONNECT_BY_ROOT T0$0.SVID AS SVID, T0$0.DVID AS DVID
FROM(
SELECT T0$0.SVID AS SVID,
T0$0.DVID AS DVID
FROM "SCOTT".TEST_GRAPHGT$ T0$0
WHERE (T0$0.EL = n'friendOf' AND T0$0.EL IS NOT NULL)) T0$0
START WITH T0$0.SVID = 2
CONNECT BY NOCYCLE PRIOR DVID = SVID))/*]Path*/) T0$0
WHERE T0$0.SVID = 2)

デフォルトの再帰的WITH戦略を使用する最初の問合せの問合せ計画は、次のようになります。

-- default RW
---------------------------------------------------------------------------------------
| Id  | Operation                                         | Name                      |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                                  |                           |
|   1 |  TEMP TABLE TRANSFORMATION                        |                           |
|   2 |   LOAD AS SELECT (CURSOR DURATION MEMORY)         | SYS_TEMP_0FD9D6662_37AA44 |
|   3 |    UNION ALL (RECURSIVE WITH) BREADTH FIRST       |                           |
|   4 |     PX COORDINATOR                                |                           |
|   5 |      PX SEND QC (RANDOM)                          | :TQ20000                  |
|   6 |       LOAD AS SELECT (CURSOR DURATION MEMORY)     | SYS_TEMP_0FD9D6662_37AA44 |
|   7 |        PX PARTITION HASH ALL                      |                           |
|*  8 |     TABLE ACCESS BY LOCAL INDEX ROWID BATCHED     | TEST_GRAPHGT$             |
|*  9 |      INDEX RANGE SCAN                             | TEST_GRAPHXSG$            |
|  10 |     PX COORDINATOR                                |                           |
|  11 |      PX SEND QC (RANDOM)                          | :TQ10000                  |
|  12 |       LOAD AS SELECT (CURSOR DURATION MEMORY)     | SYS_TEMP_0FD9D6662_37AA44 |
|  13 |        NESTED LOOPS                               |                           |
|  14 |     PX BLOCK ITERATOR                             |                           |
|* 15 |      TABLE ACCESS FULL                            | SYS_TEMP_0FD9D6662_37AA44 |
|  16 |     PARTITION HASH ALL                            |                           |
|* 17 |      TABLE ACCESS BY LOCAL INDEX ROWID BATCHED    | TEST_GRAPHGT$             |
|* 18 |       INDEX RANGE SCAN                            | TEST_GRAPHXSG$            |
|  19 |   PX COORDINATOR                                  |                           |
|  20 |    PX SEND QC (RANDOM)                            | :TQ30001                  |
|  21 |     VIEW                                          |                           |
|  22 |      HASH UNIQUE                                  |                           |
|  23 |       PX RECEIVE                                  |                           |
|  24 |        PX SEND HASH                               | :TQ30000                  |
|  25 |     HASH UNIQUE                                   |                           |
|  26 |      VIEW                                         |                           |
|  27 |       UNION-ALL                                   |                           |
|  28 |        PX SELECTOR                                |                           |
|* 29 |         FILTER                                    |                           |
|  30 |          FAST DUAL                                |                           |
|  31 |          PARTITION HASH SINGLE                    |                           |
|* 32 |           INDEX SKIP SCAN                         | TEST_GRAPHXQV$            |
|  33 |        VIEW                                       |                           |
|* 34 |         VIEW                                      |                           |
|  35 |          PX BLOCK ITERATOR                        |                           |
|  36 |           TABLE ACCESS FULL                       | SYS_TEMP_0FD9D6662_37AA44 |
---------------------------------------------------------------------------------------

再帰的ステップにDISTINCT修飾子を追加する2番目の問合せの問合せ計画は、次のようになります。

--------------------------------------------------------------------------------------------
| Id  | Operation                                              | Name                      |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                                       |                           |
|   1 |  TEMP TABLE TRANSFORMATION                             |                           |
|   2 |   LOAD AS SELECT (CURSOR DURATION MEMORY)              | SYS_TEMP_0FD9D6669_37AA44 |
|   3 |    UNION ALL (RECURSIVE WITH) BREADTH FIRST            |                           |
|   4 |     PX COORDINATOR                                     |                           |
|   5 |      PX SEND QC (RANDOM)                               | :TQ20000                  |
|   6 |       LOAD AS SELECT (CURSOR DURATION MEMORY)          | SYS_TEMP_0FD9D6669_37AA44 |
|   7 |        PX PARTITION HASH ALL                           |                           |
|*  8 |     TABLE ACCESS BY LOCAL INDEX ROWID BATCHED          | TEST_GRAPHGT$             |
|*  9 |      INDEX RANGE SCAN                                  | TEST_GRAPHXSG$            |
|  10 |     PX COORDINATOR                                     |                           |
|  11 |      PX SEND QC (RANDOM)                               | :TQ10001                  |
|  12 |       LOAD AS SELECT (CURSOR DURATION MEMORY)          | SYS_TEMP_0FD9D6669_37AA44 |
|  13 |        SORT GROUP BY                                   |                           |
|  14 |     PX RECEIVE                                         |                           |
|  15 |      PX SEND HASH                                      | :TQ10000                  |
|  16 |       SORT GROUP BY                                    |                           |
|  17 |        NESTED LOOPS                                    |                           |
|  18 |         PX BLOCK ITERATOR                              |                           |
|* 19 |          TABLE ACCESS FULL                             | SYS_TEMP_0FD9D6669_37AA44 |
|  20 |         PARTITION HASH ALL                             |                           |
|* 21 |          TABLE ACCESS BY LOCAL INDEX ROWID BATCHED     | TEST_GRAPHGT$             |
|* 22 |           INDEX RANGE SCAN                             | TEST_GRAPHXSG$            |
|  23 |   PX COORDINATOR                                       |                           |
|  24 |    PX SEND QC (RANDOM)                                 | :TQ30001                  |
|  25 |     VIEW                                               |                           |
|  26 |      HASH UNIQUE                                       |                           |
|  27 |       PX RECEIVE                                       |                           |
|  28 |        PX SEND HASH                                    | :TQ30000                  |
|  29 |     HASH UNIQUE                                        |                           |
|  30 |      VIEW                                              |                           |
|  31 |       UNION-ALL                                        |                           |
|  32 |        PX SELECTOR                                     |                           |
|* 33 |         FILTER                                         |                           |
|  34 |          FAST DUAL                                     |                           |
|  35 |          PARTITION HASH SINGLE                         |                           |
|* 36 |           INDEX SKIP SCAN                              | TEST_GRAPHXQV$            |
|  37 |        VIEW                                            |                           |
|* 38 |         VIEW                                           |                           |
|  39 |          PX BLOCK ITERATOR                             |                           |
|  40 |           TABLE ACCESS FULL                            | SYS_TEMP_0FD9D6669_37AA44 |
--------------------------------------------------------------------------------------------

CONNECTY BYを使用する3番目の問合せの問合せ計画は、次のようになります。

-----------------------------------------------------------------------------
| Id  | Operation                                      | Name               |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT                               |                    |
|   1 |  VIEW                                          |                    |
|   2 |   HASH UNIQUE                                  |                    |
|   3 |    VIEW                                        |                    |
|   4 |     UNION-ALL                                  |                    |
|*  5 |      FILTER                                    |                    |
|   6 |       FAST DUAL                                |                    |
|   7 |       PARTITION HASH SINGLE                    |                    |
|*  8 |        INDEX SKIP SCAN                         | TEST_GRAPHXQV$     |
|*  9 |      VIEW                                      |                    |
|* 10 |       CONNECT BY WITH FILTERING                |                    |
|  11 |        PX COORDINATOR                          |                    |
|  12 |     PX SEND QC (RANDOM)                        | :TQ10000           |
|  13 |      PX PARTITION HASH ALL                     |                    |
|* 14 |       TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| TEST_GRAPHGT$      |
|* 15 |        INDEX RANGE SCAN                        | TEST_GRAPHXSG$     |
|  16 |        NESTED LOOPS                            |                    |
|  17 |     CONNECT BY PUMP                            |                    |
|  18 |     PARTITION HASH ALL                         |                    |
|* 19 |      TABLE ACCESS BY LOCAL INDEX ROWID BATCHED | TEST_GRAPHGT$      |
|* 20 |       INDEX RANGE SCAN                         | TEST_GRAPHXSG$     |
-----------------------------------------------------------------------------

例5-19 PgqlExample13.java

PgqlExample13.javaは、パス問合せ評価中に長さ制限を設定する方法を示しています。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to use the maximum path length option for 
 * PGQL path queries.
 */
public class PgqlExample13
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Path Query to illustrate options
      String pgql = 
        "PATH fof AS ()-[:\"friendOf\"]->() "+
        "SELECT v1.\"fname\" AS fname1, v2.\"fname\" AS fname2 "+
        "FROM MATCH (v1)-/:fof*/->(v2) "+
        "WHERE v1.\"fname\" = 'Ray'";


      // execute query for 1-hop
      rs = ps.executeQuery(pgql, " MAX_PATH_LEN=1 ");

      // print results
      System.out.println("-- Results for 1-hop ----------------");
      rs.print();

      // close result set
      rs.close();

      // execute query for 2-hop
      rs = ps.executeQuery(pgql, " MAX_PATH_LEN=2 ");

      // print results
      System.out.println("-- Results for 2-hop ----------------");
      rs.print();

      // close result set
      rs.close();

      // execute query for 3-hop
      rs = ps.executeQuery(pgql, " MAX_PATH_LEN=3 ");

      // print results
      System.out.println("-- Results for 3-hop ----------------");
      rs.print();

      // close result set
      rs.close();

    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample13.javaには、test_graph (GraphLoaderExample.javaコードを使用してロード可能)に対する次の出力があります。

-- Results for 1-hop ----------------
+-----------------+
| FNAME1 | FNAME2 |
+-----------------+
| Ray    | Ray    |
| Ray    | Susan  |
+-----------------+
-- Results for 2-hop ----------------
+-----------------+
| FNAME1 | FNAME2 |
+-----------------+
| Ray    | Susan  |
| Ray    | Ray    |
| Ray    | John   |
+-----------------+
-- Results for 3-hop ----------------
+-----------------+
| FNAME1 | FNAME2 |
+-----------------+
| Ray    | Susan  |
| Ray    | Bill   |
| Ray    | Ray    |
| Ray    | John   |
+-----------------+
5.9.4.5.4 Partialオブジェクトの構築のオプション

問合せ結果からエッジを読み込んでいるとき、開始および終了頂点をローカル・キャッシュに追加する際に、次の2つ動作が可能です。

  • エッジ自体から入手可能な頂点IDのみを追加します。このオプションは、効率化のためのデフォルトです。

  • 項点IDを追加し、開始および終了頂点のすべてのプロパティを取得します。この動作では、PGQL問合せ結果セットから作成された各OracleVertexオブジェクトでsetPartial(true)を呼び出せます。

例5-20 PgqlExample14.java

PgqlExample14.javaは、この動作の違いを示しています。このプログラムは、まずすべてのエッジを取得するための問合せを実行します。それにより、インシデントの頂点がローカル・キャッシュに追加されます。2番目の問合せはすべての頂点を取得します。次に、プログラムは、各OracleVertexオブジェクトを表示して、どのプロパティがロードされているかを示します。

import java.sql.Connection;

import oracle.pg.rdbms.Oracle;
import oracle.pg.rdbms.OraclePropertyGraph;
import oracle.pg.rdbms.OracleVertex;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows the behavior of setPartial(true) for OracleVertex objects
 * created from PGQL query results.
 */
public class PgqlExample14
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    Oracle oracle = null;
    OraclePropertyGraph opg = null;
    PgqlStatement ps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Query to illustrate set partial
      String pgql =
        "SELECT id(e), label(e) "+
        "FROM MATCH (v1)-[e:\"knows\"]->(v2)";

      // execute query
      rs = ps.executeQuery(pgql, " ");

      // print results
      System.out.println("-- Results for edge query -----------------");
      rs.print();

      // close result set
      rs.close();

      // Create an Oracle Property Graph instance
      oracle = new Oracle(conn);
      opg = OraclePropertyGraph.getInstance(oracle,graph);

      // Query to retrieve vertices
      pgql =
        "SELECT id(v) "+
        "FROM MATCH (v)";

      // Get each vertex object in result and print with toString()
      rs = ps.executeQuery(pgql, " ");

      // iterate through result
      System.out.println("-- Vertex objects retrieved from vertex query --");
      while (rs.next()) {
        Long vid = rs.getLong(1);
        OracleVertex v = OracleVertex.getInstance(opg, vid);
        System.out.println(v.toString());
      }
      // close result set
      rs.close();

      // Execute the same query but call setPartial(true) for each vertex
      rs = ps.executeQuery(pgql, " ");
      System.out.println("-- Vertex objects retrieved from vertex query with setPartial(true) --");
      while (rs.next()) {
        Long vid = rs.getLong(1);
        OracleVertex v = OracleVertex.getInstance(opg, vid);
        v.setPartial(true);
        System.out.println(v.toString());
      }
      // close result set
      rs.close();
    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
      // close the property graph
      if (opg != null) {
        opg.close();
      }
      // close oracle
      if (oracle != null) {
        oracle.dispose();
      }
    }
  }
}

PgqlExample14.javaの出力(GraphLoaderExample.javaコードを使用してロードできます)は、次のとおりです。

-- Results for edge query -----------------
+------------------+
| id(e) | label(e) |
+------------------+
| 6     | knows    |
| 11    | knows    |
| 10    | knows    |
| 5     | knows    |
| 4     | knows    |
| 13    | knows    |
| 9     | knows    |
| 12    | knows    |
| 8     | knows    |
| 7     | knows    |
| 14    | knows    |
| 15    | knows    |
+------------------+
-- Vertex objects retrieved from vertex query --
Vertex ID 3 [NULL] {}
Vertex ID 0 [NULL] {}
Vertex ID 2 [NULL] {}
Vertex ID 1 [NULL] {}
-- Vertex objects retrieved from vertex query with setPartial(true) --
Vertex ID 3 [NULL] {bval:bol:false, fname:str:Susan, lname:str:Blue, mval:bol:false, age:int:35}
Vertex ID 0 [NULL] {bval:bol:true, fname:str:Bill, lname:str:Brown, mval:str:y, age:int:40}
Vertex ID 2 [NULL] {fname:str:Ray, lname:str:Green, mval:dat:1985-01-01 04:00:00.0, age:int:41}
Vertex ID 1 [NULL] {bval:bol:true, fname:str:John, lname:str:Black, mval:int:27, age:int:30}
5.9.4.6 別のユーザーのプロパティ・グラフの問合せ

データベースの適切な権限を付与されている場合は、別のユーザーのプロパティ・グラフのデータを問い合せることができます。たとえば、SCOTTのスキーマでGRAPH1を問い合せるには、SCOTT.GRAPH1GE$、SCOTT.GRAPH1VT$、SCOTT.GRAPH1GT$およびSCOTT.GRAPH1VD$に対するREAD権限が必要です。

例5-21 PgqlExample15.java

PgqlExample15.javaは、他のユーザーがSCOTTのスキーマ内のグラフを問い合せる方法を示しています。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to query a property graph located in another user's
 * schema. READ privilege on GE$, VT$, GT$ and VD$ tables for the other user's
 * property graph are required to avoid ORA-00942: table or view does not exist.
 */
public class PgqlExample15
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Set schema so that we can query Scott's graph
      pgqlConn.setSchema("SCOTT");

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Execute query to get a ResultSet object
      String pgql = 
        "SELECT v.\"fname\" AS fname, v.\"lname\" AS lname "+
        "FROM MATCH (v)";
      rs = ps.executeQuery(pgql, "");


      // Print query results
      rs.print();
    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

次のSQL文は、データベース・ユーザーUSER2を作成し、必要な権限を付与します。また、OraclePropertyGraph.grantAccess Java APIを使用しても同じ効果を得ることができます。

SQL> grant connect, resource, unlimited tablespace to user2 identified by user2;

Grant succeeded.

SQL> grant read on scott.test_graphvt$ to user2;

Grant succeeded.

SQL> grant read on scott.test_graphge$ to user2;

Grant succeeded.

SQL> grant read on scott.test_graphgt$ to user2;

Grant succeeded.

SQL> grant read on scott.test_graphvd$ to user2;

Grant succeeded.

データベースにUSER2として接続した場合のtest_graphデータ・セットのPgqlExample15.javaの出力は次のようになります。PgqlExample15を実行する前に、test_graphは(GraphLoaderExample.javaコードを使用して)ユーザーSCOTTによってGRAPH1としてすでにロードされている必要があることに注意してください。

+---------------+
| FNAME | LNAME |
+---------------+
| Susan | Blue  |
| Bill  | Brown |
| Ray   | Green |
| John  | Black |
+---------------+
5.9.4.7 PGQLでの問合せオプティマイザ・ヒントの使用

Java APIでは、PGQL問合せを実行する際の結合タイプに影響を与える問合せオプティマイザ・ヒントが許可されます。PgqlStatementおよびPgqlPreparedStatement内のexecuteQueryメソッドおよびtranslateQueryメソッドは、対応するSQL問合せの問合せ計画に影響を与えるために、options引数で次の文字列を受け入れます。

  • ALL_EDGE_NL – $GE表と$GT表に関連するすべての結合にネステッド・ループ結合を使用します。

  • ALL_EDGE_HASH – $GE表と$GT表に関連するすべての結合にHASH結合を使用します。

  • ALL_VERTEX_NL – $VT表に関連するすべての結合にネステッド・ループ結合を使用します。

  • ALL_VERTEX_HASH – $VT表に関連するすべての結合にHASH結合を使用します。

例5-22 PgqlExample16.java

PgqlExample16.javaは、オプティマイザ・ヒントを使用して、グラフ・トラバースに使用される結合に影響を与える方法を示しています。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlSqlQueryTrans;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to use query optimizer hints with PGQL queries.
 */
public class PgqlExample16
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();
      // Query to illustrate join hints
      String pgql = 
        "SELECT id(v1), id(v4) "+
        "FROM MATCH (v1)-[:\"friendOf\"]->(v2)-[:\"friendOf\"]->(v3)-[:\"friendOf\"]->(v4)";

      // get SQL translation with hash join hint
      PgqlSqlQueryTrans sqlTrans = 
        ps.translateQuery(pgql /* query string */, 
                          " ALL_EDGE_HASH " /* options */);
      // print SQL translation
      System.out.println("-- Query with ALL_EDGE_HASH --------------------");
      System.out.println(sqlTrans.getSqlTranslation()+"\n");

      // get SQL translation with nested loop join hint
      sqlTrans = 
        ps.translateQuery(pgql /* query string */, 
                          " ALL_EDGE_NL " /* options */);
      // print SQL translation
      System.out.println("-- Query with ALL_EDGE_NL ---------------------");
      System.out.println(sqlTrans.getSqlTranslation()+"\n");
    }
    finally {
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

test_graphPgqlExample16.javaの出力(GraphLoaderExample.javaコードを使用してロードできます)は、次のとおりです。

-- Query with ALL_EDGE_HASH --------------------
SELECT /*+ USE_HASH(T0$0 T0$1 T0$2) */ 7 AS "id(v1)$T",
to_nchar(T0$0.SVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v1)$V",
T0$0.SVID AS "id(v1)$VN",
to_timestamp_tz(null) AS "id(v1)$VT",
7 AS "id(v4)$T",
to_nchar(T0$2.DVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v4)$V",
T0$2.DVID AS "id(v4)$VN",
to_timestamp_tz(null) AS "id(v4)$VT"
FROM "SCOTT".TEST_GRAPHGT$ T0$0,
"SCOTT".TEST_GRAPHGT$ T0$1,
"SCOTT".TEST_GRAPHGT$ T0$2
WHERE T0$0.DVID=T0$1.SVID AND
T0$1.DVID=T0$2.SVID AND
(T0$0.EL = n'friendOf' AND T0$0.EL IS NOT NULL) AND
(T0$1.EL = n'friendOf' AND T0$1.EL IS NOT NULL) AND
(T0$2.EL = n'friendOf' AND T0$2.EL IS NOT NULL)

-- Query with ALL_EDGE_NL ---------------------
SELECT /*+ USE_NL(T0$0 T0$1 T0$2) */ 7 AS "id(v1)$T",
to_nchar(T0$0.SVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v1)$V",
T0$0.SVID AS "id(v1)$VN",
to_timestamp_tz(null) AS "id(v1)$VT",
7 AS "id(v4)$T",
to_nchar(T0$2.DVID,'TM9','NLS_Numeric_Characters=''.,''') AS "id(v4)$V",
T0$2.DVID AS "id(v4)$VN",
to_timestamp_tz(null) AS "id(v4)$VT"
FROM "SCOTT".TEST_GRAPHGT$ T0$0,
"SCOTT".TEST_GRAPHGT$ T0$1,
"SCOTT".TEST_GRAPHGT$ T0$2
WHERE T0$0.DVID=T0$1.SVID AND
T0$1.DVID=T0$2.SVID AND
(T0$0.EL = n'friendOf' AND T0$0.EL IS NOT NULL) AND
(T0$1.EL = n'friendOf' AND T0$1.EL IS NOT NULL) AND
(T0$2.EL = n'friendOf' AND T0$2.EL IS NOT NULL)

ALL_EDGE_HASHを使用する最初の問合せの問合せ計画は次のようになります。

-----------------------------------------------
| Id  | Operation             | Name          |
-----------------------------------------------
|   0 | SELECT STATEMENT      |               |
|*  1 |  HASH JOIN            |               |
|*  2 |   HASH JOIN           |               |
|   3 |    PARTITION HASH ALL |               |
|*  4 |     TABLE ACCESS FULL | TEST_GRAPHGT$ |
|   5 |    PARTITION HASH ALL |               |
|*  6 |     TABLE ACCESS FULL | TEST_GRAPHGT$ |
|   7 |   PARTITION HASH ALL  |               |
|*  8 |    TABLE ACCESS FULL  | TEST_GRAPHGT$ |
-----------------------------------------------

ALL_EDGE_NLを使用する2番目の問合せの問合せ計画は次のようになります。

-----------------------------------------------------------------------
| Id  | Operation                                    | Name           |
-----------------------------------------------------------------------
|   0 | SELECT STATEMENT                             |                |
|   1 |  NESTED LOOPS                                |                |
|   2 |   NESTED LOOPS                               |                |
|   3 |    PARTITION HASH ALL                        |                |
|*  4 |     TABLE ACCESS FULL                        | TEST_GRAPHGT$  |
|   5 |    PARTITION HASH ALL                        |                |
|*  6 |     TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| TEST_GRAPHGT$  |
|*  7 |      INDEX RANGE SCAN                        | TEST_GRAPHXSG$ |
|   8 |   PARTITION HASH ALL                         |                |
|*  9 |    TABLE ACCESS BY LOCAL INDEX ROWID BATCHED | TEST_GRAPHGT$  |
|* 10 |     INDEX RANGE SCAN                         | TEST_GRAPHXSG$ |
-----------------------------------------------------------------------

5.9.5 INSERT、UPDATEおよびDELETE文によるプロパティ・グラフの変更

PGQLは、プロパティ・グラフでのINSERT、UPDATEおよびDELETE操作をサポートしています。このようなDML操作は、PgqlStatementのメソッドexecuteによって実行できます。このトピックでは、このような操作の例をいくつか示します。

ノート:

INSERT、UPDATEおよびDELETE文を実行できるようにするには、JDBC接続の自動コミットをオフにする必要があります。

例5-23 PgqlExample17.java (挿入)

PgqlExample17.javaは、複数の頂点とエッジをグラフに挿入します。頂点およびエッジのID値の定義には、特殊なプロパティ_ora_idが使用されていることに注意してください。プロパティ_ora_idを省略すると、グラフに挿入される新しい頂点またはエッジごとに一意のIDが生成されます。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to execute a PGQL INSERT operation.
 */
public class PgqlExample17
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();
      conn.setAutoCommit(false);

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Execute insert statement
      String pgql = 
        "INSERT VERTEX p1 LABELS (person) PROPERTIES (p1.\"_ora_id\" = 1, p1.fname = 'Jake') "+
        "     , VERTEX p2 LABELS (person) PROPERTIES (p2.\"_ora_id\" = 2, p2.fname = 'Amy')  "+
        "     , VERTEX p3 LABELS (person) PROPERTIES (p3.\"_ora_id\" = 3, p3.fname = 'Erik') "+
        "     , VERTEX p4 LABELS (person) PROPERTIES (p4.\"_ora_id\" = 4, p4.fname = 'Jane') "+
        "     , EDGE e1 BETWEEN p1 AND p2 LABELS (knows) PROPERTIES (e1.\"_ora_id\" = 1, e1.since = DATE '2003-04-21') "+
        "     , EDGE e2 BETWEEN p1 AND p3 LABELS (knows) PROPERTIES (e2.\"_ora_id\" = 2, e2.since = DATE '2010-02-10') "+
        "     , EDGE e3 BETWEEN p3 AND p4 LABELS (knows) PROPERTIES (e3.\"_ora_id\" = 3, e3.since = DATE '1999-01-03') ";
      ps.execute(pgql, /*  query string  */ 
                   "", /* query options  */
                   ""  /* modify options */);

      // Execute a query to verify insertion
      pgql = 
          "  SELECT id(p1) AS id1, p1.fname AS person1, id(p2) as id2, p2.fname AS person2, id(e) as e, e.since "+
          "    FROM MATCH (p1)-[e:knows]->(p2) "+
          "ORDER BY id1, id2";
      rs = ps.executeQuery(pgql, "");

      // Print the results
      rs.print();
    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample17.javaの出力は次のとおりです。

+-----------------------------------------------------------+
| ID1 | PERSON1 | ID2 | PERSON2 | E | SINCE                 |
+-----------------------------------------------------------+
| 1   | Jake    | 2   | Amy     | 1 | 2003-04-20 17:00:00.0 |
| 1   | Jake    | 3   | Erik    | 2 | 2010-02-09 16:00:00.0 |
| 3   | Erik    | 4   | Jane    | 3 | 1999-01-02 16:00:00.0 |
+-----------------------------------------------------------+

INSERT文のその他の例については、こちらのPGQL仕様の関連するセクションを参照してください。

例5-24 PgqlExample18.java (更新)

PgqlExample18.javaは、UPDATE文のFROM句で一致する頂点とエッジのいくつかのプロパティを更新します。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to execute a PGQL UPDATE operation.
 */
public class PgqlExample18
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();
      conn.setAutoCommit(false);

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Execute update statement
      String pgql = 
        "UPDATE p1 SET (p1.age = 47, p1.lname = 'Red'), "+
        "       p2 SET (p2.age = 29, p2.lname = 'White'), "+
        "        e SET (e.strength = 100) "+
        "FROM MATCH (p1) -[e:knows]-> (p2) "+
        "WHERE p1.fname = 'Jake' AND p2.fname = 'Amy'";
      ps.execute(pgql, /*  query string  */ 
                   "", /* query options  */
                   ""  /* modify options */);

      // Execute a query to verify update
      pgql = 
          "SELECT p1.fname AS fname1, p1.lname AS lname1, p1.age AS age1, "+
          "       p2.fname AS fname2, p2.lname AS lname2, p2.age AS age2, e.strength "+
          "FROM MATCH (p1) -[e:knows]-> (p2)";
      rs = ps.executeQuery(pgql, "");

      // Print the results
      rs.print();
    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

以前PgqlExample17.javaが実行されたグラフに適用されたPgqlExample18.javaの出力を次に示します。

+----------------------------------------------------------------+
| FNAME1 | LNAME1 | AGE1   | FNAME2 | LNAME2 | AGE2   | STRENGTH |
+----------------------------------------------------------------+
| Jake   | Red    | 47     | Amy    | White  | 29     | 100      |
| Jake   | Red    | 47     | Erik   | <null> | <null> | <null>   |
| Erik   | <null> | <null> | Jane   | <null> | <null> | <null>   |
+----------------------------------------------------------------+

UPDATE文のその他の例については、こちらのPGQL仕様の関連するセクションを参照してください。

例5-25 PgqlExample19.java (削除)

PgqlExample19.javaは、DELETE文のFROM句と一致するエッジを削除します。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to execute a PGQL DELETE operation.
 */
public class PgqlExample19
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();
      conn.setAutoCommit(false);

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Execute delete statement
      String pgql = 
        "DELETE e "+
        "  FROM MATCH (p1) -[e:knows]-> (p2) "+
        " WHERE p1.fname = 'Jake'";
      ps.execute(pgql, /*  query string  */ 
                   "", /* query options  */
                   ""  /* modify options */);

      // Execute a query to verify delete
      pgql = 
          "SELECT p1.fname AS fname1, p2.fname AS fname2 "+
          "  FROM MATCH (p1) -[e:knows]-> (p2)";
      rs = ps.executeQuery(pgql, "");

      // Print the results
      rs.print();
    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

以前PgqlExample18.javaが実行されたグラフに適用されたPgqlExample19.javaの出力を次に示します。

+-----------------+
| FNAME1 | FNAME2 |
+-----------------+
| Erik   | Jane   |
+-----------------+

DELETE文のその他の例については、こちらのPGQL仕様の関連するセクションを参照してください。

例5-26 PgqlExample20.java (複数の変更)

PgqlExample20.javaは、同じ文で複数の変更を実行します。エッジの挿入、頂点プロパティの更新、別のエッジの削除が行われます。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to execute a PGQL 
 * INSERT/UPDATE/DELETE operation.
 */
public class PgqlExample20
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();
      conn.setAutoCommit(false);

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Execute INSERT/UPDATE/DELETE statement
      String pgql = 
        "INSERT EDGE f BETWEEN p2 AND p1 LABELS (knows) PROPERTIES (f.since = e.since) "+
        "UPDATE p1 SET (p1.age = 30) "+
        "     , p2 SET (p2.age = 25) "+
        "DELETE e "+
        "  FROM MATCH (p1) -[e:knows]-> (p2) "+
        " WHERE p1.fname = 'Erik'";
      ps.execute(pgql, /*  query string  */ 
                   "", /* query options  */
                   ""  /* modify options */);

      // Execute a query to verify INSERT/UPDATE/DELETE
      pgql = 
          "SELECT p1.fname AS fname1, p1.age AS age1, "+
          "       p2.fname AS fname2, p2.age AS age2, e.since "+
          "  FROM MATCH (p1) -[e:knows]-> (p2)";
      rs = ps.executeQuery(pgql, "");

      // Print the results
      rs.print();
    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }

以前PgqlExample19.javaが実行されたグラフに適用されたPgqlExample20.javaの出力を次に示します。

+-------------------------------------------------------+
| FNAME1 | AGE1 | FNAME2 | AGE2 | SINCE                 |
+-------------------------------------------------------+
| Jane   | 25   | Erik   | 30   | 1999-01-02 16:00:00.0 |
+-------------------------------------------------------+

INSERT、UPDATE、DELETE文のその他の例については、こちらのPGQL仕様の関連するセクションを参照してください。

5.9.5.1 PGQL文実行の追加オプション

PGQL文の実行に影響を与える複数のオプションがあります。問合せオプションを設定する主な方法は次のとおりです。

  • executemodify optionsに対して文字列引数のフラグを使用する
  • Java JVM引数を使用する

次の表に、PGQL文の実行を変更するための主なオプションをまとめます。

表5-3 PGQL文の変更オプション

オプション デフォルト オプション・フラグ JVM引数
自動コミット JDBC自動コミットがオフの場合はtrue、JDBC自動コミットがオンの場合はfalse AUTO_COMMIT=F -Doracle.pg.rdbms.pgql.autoCommit=false
カスケードの削除 true DELETE_CASCADE=F -Doracle.pg.rdbms.pgql.deleteCascade=false
5.9.5.1.1 PGQL自動コミットの無効化

INSERT、UPDATEまたはDELETE操作が実行されると、PGQL実行の最後にコミットが自動的に実行され、RDBMS側で変更が持続されます。

フラグAUTO_COMMIT=Fexecuteoptions引数に追加するか、フラグDoracle.pg.rdbms.pgql.autoCommit=falseをJavaコマンド・ラインで設定して、自動コミットをオフにすることができます。自動コミットがオフの場合は、グラフの変更を持続または取り消すために、JDBC接続で必要なコミットまたはロールバックを実行する必要があります。

例5-27 自動コミットおよびロールバック変更の無効化

PgqlExample21.javaは、自動コミットをオフにして、変更のロールバックを実行します。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlResultSet;
import oracle.pg.rdbms.pgql.PgqlStatement;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows how to modify a PGQL graph
 * with auto commit off.
 */
public class PgqlExample21
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;
    PgqlResultSet rs = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();
      conn.setAutoCommit(false);

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Delete all the edges in the graph
      String pgql = 
        "DELETE e "+
        "  FROM MATCH () -[e]-> ()";
      ps.execute(pgql, /*  query string  */ 
                 "",   /* query options  */
                 "AUTO_COMMIT=F"  /* modify options */);

      // Execute a query to verify deletion
      pgql = 
          "SELECT COUNT(e) "+
          "  FROM MATCH () -[e]-> ()";
      rs = ps.executeQuery(pgql, "");

      // Print the results
      System.out.println("Number of edges after deletion:");
      rs.print();
      rs.close();

      // Rollback the changes. This is possible because
      // AUTO_COMMIT=F flag was used in execute
      conn.rollback();

      // Execute a query to verify rollback
      pgql = 
          "SELECT COUNT(e) "+
          "  FROM MATCH () -[e]-> ()";
      rs = ps.executeQuery(pgql, "");

      // Print the results
      System.out.println("Number of edges after rollback:");
      rs.print();
    }
    finally {
      // close the result set
      if (rs != null) {
        rs.close();
      }
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample21.javaでは、エッジが1つあるグラフの場合、次に示す出力が得られます。

Number of edges after deletion:
+----------+
| COUNT(e) |
+----------+
| 0        |
+----------+
Number of edges after rollback:
+----------+
| COUNT(e) |
+----------+
| 1        |
+----------+
5.9.5.1.2 カスケード削除の無効化

グラフから頂点を削除すると、その入力と出力のエッジもすべて自動的に削除されます。

フラグを設定するexecuteoptions引数でフラグDELETE_CASCADE=Fを使用するか、Javaコマンド・ラインでフラグDoracle.pg.rdbms.pgql.autoCommit=falseを設定すると、カスケード削除をオフにできます。入力エッジまたは出力エッジのある頂点が削除され、カスケード削除がオフになっている場合は、実行しようとしている安全でない操作について警告するエラーがスローされます。

例5-28 カスケード削除の無効化

PgqlExample22.javaは、カスケード削除がオフの場合に出力エッジのある頂点を削除しようとします。

import java.sql.Connection;

import oracle.pg.rdbms.pgql.PgqlConnection;
import oracle.pg.rdbms.pgql.PgqlStatement;
import oracle.pg.rdbms.pgql.PgqlToSqlException;

import oracle.ucp.jdbc.PoolDataSourceFactory;
import oracle.ucp.jdbc.PoolDataSource;

/**
 * This example shows the use of DELETE_CASCADE flag.
 */
public class PgqlExample22
{

  public static void main(String[] args) throws Exception
  {
    int idx=0;
    String host               = args[idx++]; 
    String port               = args[idx++]; 
    String sid                = args[idx++]; 
    String user               = args[idx++]; 
    String password           = args[idx++];
    String graph              = args[idx++];

    Connection conn = null;
    PgqlStatement ps = null;

    try {

      //Get a jdbc connection
      PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
      pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
      pds.setURL("jdbc:oracle:thin:@"+host+":"+port +":"+sid);
      pds.setUser(user);
      pds.setPassword(password);     
      conn = pds.getConnection();
      conn.setAutoCommit(false);

      // Get a PGQL connection
      PgqlConnection pgqlConn = PgqlConnection.getConnection(conn);
      pgqlConn.setGraph(graph);

      // Create a PgqlStatement
      ps = pgqlConn.createStatement();

      // Delete all the vertices with output edges
      // This will throw an error
      String pgql = 
        "DELETE v "+
        "  FROM MATCH (v) -[e]-> ()";
      ps.execute(pgql, /*  query string  */ 
                 "",   /* query options  */
                 "DELETE_CASCADE=F"  /* modify options */);
    }
    catch (PgqlToSqlException ex){
      System.out.println("Error in execution: " + ex.getMessage());
    }
    finally {
      // close the statement
      if (ps != null) {
        ps.close();
      }
      // close the connection
      if (conn != null) {
        conn.close();
      }
    }
  }
}

PgqlExample22.javaでは、少なくともエッジが1つあるグラフの場合、次に示す出力が得られます。

Error in execution: Attempting to delete vertices with incoming/outgoing edges. Drop edges first or turn on DELETE_CASCADE option

5.9.6 PGQL問合せでのパフォーマンスの考慮事項

多数の要因が、Oracle DatabaseでのPGQL問合せのパフォーマンスに影響を与えます。次に、問合せのパフォーマンスのためのお薦めの慣例をいくつか示します。

問合せオプティマイザ統計

優れた、最新の問合せオプティマイザ統計は、問合せのパフォーマンスにとって非常に重要です。プロパティ・グラフのデータを大幅に更新した後は、必ずOPG_APIS.ANALYZE_PGを実行してください。

パラレル問合せの実行

パラレル問合せ実行を使用して、OracleのパラレルSQLエンジンを利用します。多くの場合、パラレル実行は、シリアル実行に対して大幅なスピードアップになります。パラレル実行は、再帰的WITH戦略を使用して評価されるパス問合せで特に重要となります。

パラレル問合せ実行の詳細は、『Oracle Database VLDBおよびパーティショニング・ガイド』も参照してください。

オプティマイザの動的サンプリング

グラフ・データ・モデル本来の柔軟性のため、静的情報では必ずしも最適な問合せ計画が生成されない場合があります。そのような場合は、よりすぐれた問合せ計画を得るために、問合せオプティマイザで動的サンプリングを使用して、実行時にデータのサンプリングを実行できます。サンプリングされるデータ量は、使用される動的サンプリング・レベルによって制御されます。動的サンプリング・レベルの範囲は0から11です。最適な使用レベルは、特定のデータセットとワークロードによって異なりますが、多くの場合、2 (デフォルト)、6、または11のレベルで良好な結果を得られます。

『Oracle Database SQLチューニング・ガイド』の「補足的な動的統計」も参照してください。

バインド変数

可能な場合は常に、定数にバインド変数を使用します。バインド変数の使用により、問合せのコンパイル時間が大幅に削減されます。それにより、使用されている定数値のみが異なる問合せでの問合せワークロードのスループットが大幅に増加します。さらに、バインド変数を使用した問合せでは、インジェクション攻撃に対する脆弱性が軽減されます。

パス問合せ

+(プラス記号)演算子または*(アスタリスク)演算子を使用して任意の長さのパスを検索する、PGQLのパス問合せでは、計算が非常に複雑なため、特別な考慮が必要になります。最適なパフォーマンスを得るためには、パラレル実行を使用し、再帰的WITH(USE_DIST_RW=T)にDISTINCTオプションを使用してください。また、大規模で、連結度が高いグラフの場合は、MAX_PATH_LEN=nを使用して、再帰ステップの反復数を妥当な数に制限することをお薦めします。お薦めの方法は、小さな反復制限から開始して、その制限を繰り返し増加してより多くの結果を見つけるというものです。