[Autonomous Database] データ共有で共有した表をpandasで読み込む

# はじめに
Autonomous Databaseでは、Delta Shareプロトコルを使ったデータ共有ができます。
Delta ShareはオープンソースのAPIなので、サポートされている任意の受信者(他のデータベース、データ分析ツール、オープンソース・プラットフォーム)とデータをセキュアに共有できます。

今回は、Autonomous Databaseを共有のプロバイダとして、Autonomous Database内の表を共有し、Pythonのpandasで読み込んでみたいと思います。
データ共有は、Autonomous Databaseの付属ツールであるDatabase Actionsから実行できます。
※コマンドでも同様に実行できます。

# データ共有へのアクセス
ADMINユーザーで以下を実行することで、特定のユーザーがデータ共有へアクセスすることができます。
```sql
begin
    DBMS_SHARE.ENABLE_SCHEMA(
            SCHEMA_NAME => '<ユーザー名>',
            ENABLED => TRUE
    );
    commit;
end;
```

# 共有の作成
1. 上記で有効化されたユーザーでDatabase Actionsにサインインすると、以下のようにData Studio内に`データ共有`が表示されます。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/8c585104-86b5-84e6-0a0e-a52a436a7e4d.png)

1. クリックすると、`共有の提供`と`共有の消費`が選択できます。今回はAutonomous Databaseから表データを共有するので、`共有の提供`をクリックします。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/f6780e34-1cc6-8edd-4f92-676f93075b9d.png)

1. `+ 共有の作成`をクリックします。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/d8917338-f6ec-f2c3-829c-23d2e7f5f917.png)

1. 名前と説明(省略可)を入力し、`次`をクリックします。
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/5ab84d7a-88d1-a044-38cb-e5f5ebc97400.png)

1. 共有を公開するクラウド(オブジェクト・ストレージ)を登録するため、`クラウドの場所の作成`をクリックします。
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/74f1534b-3af1-1813-f09a-4ee8d81fa495.png)

1. オブジェクト・ストレージへアクセスするための資格証明を作成します。
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/ecb83021-2163-ad7c-b2cc-31d86ef7b175.png)

1. 以下のように各項目入力していきます。入力項目については、<a href="https://docs.oracle.com/ja-jp/iaas/autonomous-database-shared/doc/create-oracle-cloud.html" target="_blank" rel="noopener noreferrer">こちら</a>をご参照ください。
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/2709c781-3967-29cf-da82-70b4978b8887.png)

1. 資格証明の作成ができたので、クラウド・ストアを追加します。先ほど作成した資格証明が選択されていることを確認し、バケットURIを指定します。`namespace`や`bucket-name`は適宜修正してください。
![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/67492bd0-cfad-3fef-92a0-31163e5d01c9.png)

1. `DEMO_OBJ_STORE`というOCIネイティブ資格証明を作成できたので、それを指定し、`次`をクリックします。なお、今回はスケジューリングは有効化しませんが、毎n分/毎n時間/毎n日/毎n週や開始日/終了日を指定しスケジューリングも可能です。
![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/06aba34c-30f5-714e-547b-a51ec38a2483.png)

1. 共有する表を選択します。`使用可能な表`には、`demo`スキーマの所有している表が表示されています。今回は、`MOVIE_SALES_2020Q2`という表を共有します。
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/b7decdf6-eea6-25d7-69df-aa905cf32b33.png)
![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/96dae856-1f98-a914-59dc-7c026a519251.png)

1. 共有する受信者を設定します。
![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/32f7d230-a04c-4372-281c-6012c2bf7b95.png)

1. 名前と電子メールが必須項目です。また、ユーザー・トークン存続時間の指定も可能です。
![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/15b838e0-76c7-a41a-2dbd-4311c7a02b81.png)

1. 作成すると、`SAMPLE`ユーザーに受信権限が与えられます。ただし、新規受信者はアクティベーションする必要があります。メールアイコンをクリックすると、メーラーが起動し、受信者のアドレスにプロファイルのダウンロード・リンクを含むメールを送信することができます。
![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/a471dc00-bff0-3a47-9fb6-75075de16c7d.png)

1. 以下のようなメールを送信します。受信者側の作業は後の手順で確認します。
![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/4b9a6b5a-669c-c447-5e81-0d5ece161e8b.png)

1. `作成`をクリックします。
![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/38eb5651-3a56-66aa-808a-f3958041c6df.png)

これで共有の作成は完了です。
続いて、受信者側として作業を行います。

# 共有の消費
1. 先ほどプロバイダから送信されたメール内のダウンロード・リンクをクリックします。以下のようなページに遷移します。
![17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/bd44fd27-e019-2bfb-57a5-1759a640bba2.png)

1. `Get Profile Information`をクリックすると、delta_share_profile.jsonファイルがダウンロードできます。

1. delta_share_profile.jsonを確認してみます。以下のように、エンドポイントと、ベアラートークンなどの情報が格納されています。
    ```
    {
      "shareCredentialsVersion": 1,
      "endpoint": "https://xxxxx.adb.ap-tokyo-1.oraclecloudapps.com/ords/demo/_delta_sharing",
      "tokenEndpoint": "https://xxxxx.adb.ap-tokyo-1.oraclecloudapps.com/ords/demo/oauth/token",
      "bearerToken": "aPFOYWgJz75f5_cLwZcpgw",
      "expirationTime": "2023-08-31T07:52:52.0Z",
      "clientID": "jONlE9VrRibYH0gDCILPTg..",
      "clientSecret": "WUwt2vsZyWUzOclt6Ea4mQ.."
    }
    ```
1. プロファイルを取得できたので、共有されている表の情報を取得したいと思います。今回はJupyter Notebookでpandasからロードできるか確認してみます。delta-sharingはpythonパッケージとして提供されており、pip経由でインストールできます。
    ```sh
    pip3 install delta-sharing
    ```

1. 以下のPythonプログラムをJupyter Notebook上で実行します。
    ```delta_read.py
    import requests
    import os
    import delta_sharing
    import json
    import shutil
    import datetime
    import pandas as pd
    from datetime import datetime, timedelta
    
    pd.options.display.width = 100
    def update_share_profile_token(profile_file, create_backup=True):
      # プロファイルの読み込み
      f = open(profile_file)
      data = json.load(f)
      f.close()
     
      # トークンのアップデート
      token_endpoint = data['tokenEndpoint']
      client_id = data['clientID']
      client_secret = data['clientSecret']
     
      if token_endpoint is None or client_id is None or client_secret is None:
         # 何もしない
         return
     
      # 新しいトークンの取得
      response = requests.post(
            token_endpoint,
            data={"grant_type": "client_credentials"},
            auth=(client_id, client_secret))
      response_json = response.json()
      print(response_json)
      if response_json["access_token"] is None:
        # アップデートしない
        return
      data['bearerToken']=response_json["access_token"]
      if response_json["expires_in"] is not None:
        # 有効期限のアップデート
        time_now = datetime.utcnow()
        expiration_time = time_now + timedelta(0,response_json["expires_in"])
        # デルタ・タイム・フォーマット
        data["expirationTime"] = expiration_time.strftime("%Y-%m-%dT%H:%M:%S.0Z")
       
      if create_backup:
        # プロファイル・ファイルのバックアップ作成
        shutil.copy(profile_file, profile_file+".bk")
     
      json_object = json.dumps(data, indent=2)
      with open(profile_file, "w") as outfile:
        outfile.write(json_object)
     
    # プロファイル・ファイルの指定
    profile_file = "/home/opc/python/delta_share_profile.json"
    update_share_profile_token(profile_file)
    
    # 共有クライアントの作成
    client = delta_sharing.SharingClient(profile_file)
      
    # すべての共有のリスト
    shares = client.list_shares()
    print(shares)
      
    # 最初の共有のスキーマの表示
    schemas = client.list_schemas(shares[0])
    print(schemas)
      
    # 最初の共有のテーブルの表示
    tables = client.list_tables(schemas[0])
    print(tables)
    
    
    ## pandasで10件だけロード
    for tab in tables:
      delta_path = profile_file + '#' + tab.share + '.' + tab.schema + '.' + tab.name
      df = delta_sharing.load_as_pandas(delta_path, limit=10)
      print(df)
    ```

1. 以下のように、正しく実行結果が得られました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/047441a9-a0b2-f7ba-9813-81600f2c81c0.png)


    Database Actions側で、共有を非公開にしてみます。3点リーダーをクリックし、`非公開`をクリックします。
    ![18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/222a893e-928a-0951-bbd4-e071721e477d.png)
    
    非公開にすると、以下のようにステータスが赤色になります。
    ![19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/3553dc8f-4786-005a-6b43-19cb3fb6f2de.png)
    
    この状態で、受信者側で先ほどと同様にdelta_read.pyを実行してみます。
    以下のようにlist_schemasの部分でIndexErrorになりました。
    
    ![20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/f89702b9-c204-31fa-0a4b-62538d308506.png)

    また、プロバイダ側は、共有使用状況レポートから、受信者のアクティビティを確認することができます。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/3dfbf912-028c-03cf-67da-55469bbbb1f8.png)


# おわりに
今回はAutonomous Database内の表を、pandasからロードするという内容でしたが、他にもSparkで読み込んだり、他データベース、BIツールから読み込むこともできます。

# 参考資料
+ <a href="https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/adp-data-share-tool.html#GUID-7EECE78B-336D-4853-BFC3-E78A7B8398DB" target="_blank" rel="noopener noreferrer">ドキュメント:The Data Share Tool</a>