PowerShell で RDS for Oracle からファイル転送(GET_RAW)

以前の記事「PowerShell で RDS for Oracle へファイル転送(PUT_RAW)」の逆です。RDS for Oracle から UTL_FILE.GET_RAW を使ってローカルへファイル転送してみます。今回も実行には ODP.NET managed driver を使いますので「PowerShell から ODP.NET managed driver を使って Oracle 接続する」を参考にしてください。

UTL_FILE.GET_RAW でファイル転送

Oracle からファイル転送しますので、Oracle で定義しているディレクトリオブジェクト配下に既にファイルが存在することが前提です。今回もデフォルトで存在する DATA_PUMP_DIR を利用します。

以下の SQL で DATA_PUMP_DIR 配下のファイルを確認します。前回ファイル転送した"SCOTT.DMP"が存在しますので、今度はこちらを取得するようにします。

SELECT * FROM TABLE(RDSADMIN.RDS_FILE_UTIL.LISTDIR('DATA_PUMP_DIR'));
RDS_FILE_UTIL.LISTDIR

Oracle 側のファイルの読み込みは UTL_FILE.GET_RAW が行うわけですが、ファイルの最後まで読み込めたかどうかがプロシジャの戻り値等ではなく、ORA-1403 (NO_DATA_FOUND) が発生するというところに特徴があります。呼出し元では例外を catch してあげると同時に他のエラーコードではないことを確認してあげる必要があります。

$ErrorActionPreference = "STOP"

# Loading ODP.NET
[void][Reflection.Assembly]::LoadFile("D:\ODP.NET_Managed_ODAC12cR4\odp.net\managed\common\Oracle.ManagedDataAccess.dll")

# RDS インスタンス情報
$rds_port = 1521
$rds_host = "orcl.csbouk99kbra.ap-northeast-1.rds.amazonaws.com"

$rds_user = "scott"
$rds_pass = "tiger"
$rds_sid  = "ORCL"

$dirname  = "DATA_PUMP_DIR"

# 転送ファイル情報
$indir    = "D:\TEMP"
$fname    = "SCOTT.DMP"
$chunk    = 8192 # UTL_FILE.GET_RAW の最大サイズ=32767

# 接続文字列
$constring=("User Id={0};Password={1};Data Source={2}:{3}/{4}" -f $rds_user,$rds_pass,$rds_host,$rds_port,$rds_sid)

# SQL定義
$sql_global = "CREATE OR REPLACE PACKAGE PS_GLOBAL AS FH UTL_FILE.FILE_TYPE; END;";
$sql_open   = "BEGIN PS_GLOBAL.FH := UTL_FILE.FOPEN(:DIRNAME, :FNAME, 'RB', :CHUNK); END;";
$sql_read   = "BEGIN UTL_FILE.GET_RAW(PS_GLOBAL.FH,:DATA,:CHUNK); END;";
$sql_close  = "BEGIN UTL_FILE.FCLOSE(PS_GLOBAL.FH);END;";


# 接続
$con = New-Object Oracle.ManagedDataAccess.Client.OracleConnection($constring)
$con.Open()

# グローバル変数定義
$cmd = New-Object Oracle.ManagedDataAccess.Client.OracleCommand($sql_global,$con)
[void]$cmd.ExecuteNonQuery()

# 転送先ファイルオープン
$cmd.CommandText = $sql_open
$cmd.Parameters.Clear()
[void]$cmd.Parameters.Add((New-Object Oracle.ManagedDataAccess.Client.OracleParameter "DIRNAME", $dirname))
[void]$cmd.Parameters.Add((New-Object Oracle.ManagedDataAccess.Client.OracleParameter "FNAME"  , $fname))
[void]$cmd.Parameters.Add((New-Object Oracle.ManagedDataAccess.Client.OracleParameter "CHUNK"  , $chunk))
[void]$cmd.ExecuteNonQuery()

# ファイル転送
$cmd.CommandText = $sql_read
$writer = [System.IO.File]::OpenWrite((Join-Path $indir $fname))

while( $true )
{
    # 出力パラメータ
    $dataParm = New-Object Oracle.ManagedDataAccess.Client.OracleParameter "DATA",([Oracle.ManagedDataAccess.Client.OracleDbType]::Raw),$chunk, $null,([System.Data.ParameterDirection]::Output)

    # 転送
    $cmd.Parameters.Clear()
    [void]$cmd.Parameters.Add($dataParm)
    [void]$cmd.Parameters.Add((New-Object Oracle.ManagedDataAccess.Client.OracleParameter "CHUNK", $chunk))
    try
    {
        [void]$cmd.ExecuteNonQuery()
    }
    catch [Oracle.ManagedDataAccess.Client.OracleException]
    {
        # 読み込み終了時は ORA-1403(NO_DATA_FOUND)
        if ( $Error[0].Exception.InnerException.Number -eq 1403 )
        {
            break
        }
        else
        {
            $writer.Close()
            throw
        }
    }
    # 対象ファイルに書き込み
    $data = [byte[]]$dataParm.Value
    $writer.write($data,0,$data.Length)
}
$writer.Close()

# 転送先ファイルクローズ
$cmd.CommandText=$sql_close
$cmd.Parameters.Clear()
[void]$cmd.ExecuteNonQuery()

# 切断
$con.Close()

また、このスクリプトでも前回と同様、接続したスキーマに PS_GLOBAL というパッケージが作成されますので、定常的に利用するのであれば同時実行に対する工夫が必要になってきます。