expdpの.dmpから全テーブルのcsvファイルを出力～RDS for Oarcleへimpdpするまで～

# はじめに
故あって、oracleのdmpから全テーブルのデータをcsvファイルにする必要がありました。
その際の作業内容を備忘録的に書きます。

## 環境用意
dmpを展開するためにoracleが必要です。今回はたまたま使えるAWSアカウントがあったのでRDSを立てることにしました。
RDS for Oracleの立て方、およびSQLPLUSクライアントの用意の仕方は公式が詳しいです。

https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/CHAP_GettingStarted.CreatingConnecting.Oracle.html

なお、EC2からRDSに接続するところについては、私は昔ながらのtnsnames.oraを使いました。
`$ORACLE_HOME/admin/network/tnsnames.ora`を作成し、中身はこうです。
```tnsnames.ora
ORCL =
  (DESCRIPTION = 
    (ADDRESS = (PROTOCOL = TCP)(HOST = xxxx.ap-northeast-n.rds.amazonaws.com)(PORT = 1521))
    (CONNECT_DATA =
      (SID = DATABASE)
    )
  )
```
## .dmpの配置
.dmpファイルは一般的にはimpdpコマンドで投入します。impdpは仕様上、`DATA_PUMP_DIR`に格納されているdmpファイルを投入元とすると思います。
で、実際の`DATA_PUMP_DIR`はたとえば
`D:\app\test\admin\orcl\dpdump\`
だったりするわけですが、RDSの場合はどうすればいいのでしょうか。
RDSの場合はS3から`DATA_PUMP_DIR`にダウンロードできる以下のようなSELECT文が存在します。

https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/oracle-s3-integration.html#oracle-s3-integration.using

```SQL:SQL
SELECT rdsadmin.rdsadmin_s3_tasks.download_from_s3(
 p_bucket_name => 'oracle-dmp',
 p_s3_prefix => 'datapump/',
 p_directory_name => 'DATA_PUMP_DIR')
 AS TASK_ID FROM DUAL; 
 ```
これで`DATA_PUMP_DIR`にdmpファイルがダウンロードされるわけですが、私の場合は初回次のエラーが発生しました。
```
ORA-00904:"ROSADMIN",. "RDSADMINLSS _ TASKS*. "DOMINLOAD_FROMLS3": invalid identifier
```
`無効な識別子です`ということです。一見よくわからないエラーですが、そのままググるとAWSの情報センターのページが出て来ます。

https://repost.aws/ja/knowledge-center/rds-oracle-s3-integration

記事の記載のとおり、「DBインスタンスがS3_INTEGRATIONオプションを含むオプショングループに関連付けられていないこと」が原因です。このオプションを関連付けると、PL/SQLパッケージがインストールされるのでしょう。
また、このあたりの情報は次のような記事もあります。これを上から下までひととおりやれば大丈夫そうです。

https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/oracle-s3-integration.html#oracle-s3-integration.preparing

さて、DBインスタンスにS3_INTEGRATIONオプションを含むオプショングループを関連付けたあと、再度先ほどのコマンドを実行すると、今度はタスクIDが返ります。
```SQL:SQLPLUS
SELECT rdsadmin.rdsadmin_s3_tasks.download_from_s3(
  p_bucket_name => 'oracle-dmp',
  p_s3_prefix => 'datapump/',
  p_directory_name => 'DATA_PUMP_DIR')
AS TASK_ID FROM DUAL; 

TASK_ID
----------
1694158807308-15
 ```
これでタスクが実行されていることがわかります。
このタスクの結果を見るために、次のSELECT文を発行しましょう。logファイルのTaskIDは書き換えてください。
```SQL:SQL
SELECT text FROM table
    (rdsadmin.rds_file_util.read_text_file(
        'BDUMP', 'dbtask-1694158807308-15.log'));
```
タスクが成功していたら、以下のようなTEXTが返ってきます。
```
<省略>
yyyy-MM-dd hh:mi:ss.000 UTC [INFO] This task is about to download the Amazon S3 object or objects in /rdsdbdata/datapump from bucket name oracle-dmp and key datapump/hogehoge.DMP.
yyyy-MM-dd hh:mi:ss.000 UTC [INFO] The task successfully downloaded the Amazon S3 object or objects from bucket name oracle-dmp with key datapump/hogehoge.DMP to the location /rdsdbdata/datapump.
yyyy-MM-dd hh:mi:ss.000 UTC [INFO] The task finished successfully.
```
:::note warn
DBインスタンスに適切なS3ポリシーをアタッチできていない場合、次のようなエラーでタスクが異常終了している場合があります。
```
yyyy-MM-dd hh:mi:ss.000 UTC [ERROR] The DB instance doesn't have credentials to access the specified Amazon S3 bucket. To grant access, add the S3_INTEGRATION role to the DB instance.
yyyy-MM-dd hh:mi:ss.000 UTC [INFO ] The task failed.
```
私は断片的に対応していたのでこうなってしまったのですが、上述した
[Amazon S3 と RDS for Oracle を統合する IAM アクセス許可の設定](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/oracle-s3-integration.html#oracle-s3-integration.preparing)
をひととおりちゃんとやっていれば発生しないと思います。もし発生した場合は、もう一度手順を見直しましょう。
:::

## impdp
それでは、配置したdmpをインポートします。
次のコマンドでインポートできます。
```SQL:SQL
DECLARE
hdnl NUMBER;
vMsg  VARCHAR2(2048);
BEGIN
hdnl := DBMS_DATAPUMP.open(operation=>'IMPORT', job_mode=>'SCHEMA', remote_link=>null, job_name=>null, version=>'COMPATIBLE');
DBMS_DATAPUMP.ADD_FILE(handle=>hdnl, filename=>'hogehoge.DMP', directory=>'DATA_PUMP_DIR', filetype => dbms_datapump.ku$_file_type_dump_file);
DBMS_DATAPUMP.ADD_FILE(handle=>hdnl, filename=>'IMPORT.LOG', directory => 'DATA_PUMP_DIR', filetype => dbms_datapump.ku$_file_type_log_file);
DBMS_DATAPUMP.METADATA_FILTER(hdnl,'SCHEMA_EXPR','IN (''hogehoge'')');
DBMS_DATAPUMP.start_job(hdnl);
EXCEPTION
    WHEN OTHERS THEN
        vMsg := SQLERRM(SQLCODE);
        DBMS_OUTPUT.PUT_LINE(vMsg);
END; 
/
```

https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/Oracle.Procedural.Importing.DataPump.html

成功すると、
```
PL/SQL procedure successfully completed.
```
と出力されます。
試しに適当なSQLでインポートの検証をしましょう。
```SQL:SQL
SELECT COUNT(*) FROM DBA_TABLES WHERE OWNER='hogehoge';
```
hogehogeスキーマのテーブル数が返ります。
もちろん、impdpのIMPORT.LOGを参照することも可能です。
```SQL:SQL
SELECT * FROM table
    (rdsadmin.rds_file_util.read_text_file(
        p_directory => 'DATA_PUMP_DIR',
        p_filename  => 'IMPORT.LOG'));
```

https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/Appendix.Oracle.CommonDBATasks.Misc.html#Appendix.Oracle.CommonDBATasks.ReadingFiles

見づらければ、S3を経由して手元の好きなエディタで参照しましょう。
```SQL:SQL
SELECT rdsadmin.rdsadmin_s3_tasks.upload_to_s3(
  p_bucket_name => 'oracle-dmp',
  p_directory_name => 'DATA_PUMP_DIR',
  p_s3_prefix => 'oracle-dmp/',
  p_prefix => 'IMPORT.LOG')
AS TASK_ID FROM DUAL;
```

これでRDS for Oracleへdmpを展開できました。
あとはログファイルの出力内容に応じて、統計情報の再取得など必要な処理を実行してください。
次は、ここから全テーブルをCSVファイル出力する方法を実践していきます。