2025年6月4日水曜日

BLOBとBase64エンコードしたCLOBの相互変換をPL/SQLとMLE JavaScriptで実行する

主にネットワーク上でバイナリ・データを送受信するにあたって、Base64でエンコードまたはデコードが必要な場合があります。Oracle APEXのサーバー・サイドのプロセスでの実行を想定して、PL/SQLとMLE JavaScriptで、BLOBからBase64のCLOBおよびその反対のBase64のCLOBからBLOBへ変換するコードを書いてみました。

それらのプロセスを組み込んだアプリケーションの画面です。


このAPEXアプリケーションのエクスポートを以下に置いています。
https://github.com/ujnak/apexapps/blob/master/exports/blob-handling.zip

機能はすべてホーム・ページに実装しています。

タイプファイルのアップロードであるページ・アイテムP1_FILEを作成しています。このページ・アイテムのストレージタイプ表APEX_APPLICATION_TEMP_FILESを選択することで、ボタンSUBMITをクリックしたときに実行されるプロセス内で、アップロードされたファイルをAPEX_APPLICATION_TEMP_FILESの列BLOB_CONTENTより、BLOBとして取り出せます。


ボタンSUBMITをクリックしたときに、以下の4つのプロセスを実行します(最初のプロセスInitializeはコレクションの初期化を行い、BLOBの変換処理は行いません)。
  • BLOB2BASE64_PLSQL - PL/SQLでBLOBをBase64エンコードしたCLOBへ変換します。結果をAPEXコレクションに保存します。
  • BLOB2BASE64_JS - JavaScriptでBLOBをBase64エンコードしたCLOBへ変換します。結果をAPEXコレクションに保存します。
  • BASE642BLOB_PLSQL - JavaScriptで変換したBase64のCLOBを、PL/SQLでBLOBへ変換します。結果をAPEXコレクションに保存します。
  • BASE642BLOB_JS - PL/SQLで変換したBase64のCLOBを、JavaScriptでBLOBへ変換します。結果をAPEXコレクションに保存します。
変換結果はすべてAPEXコレクションに保存されます。変換結果を確認するために、以下のSELECT文をソースとしたクラシック・レポートを作成しています。
select
    seq_id,
    c001,
    substr(clob001,1,60) clob001,
    dbms_lob.getlength(blob001) blob001
from apex_collections
where collection_name = :P1_COLLECTION_NAME
CLOBについては先頭60バイトが一致していれば、JavaScriptとPL/SQLの双方の変換で同じ、BLOBについては全体の長さが一致していればJavaScriptとPL/SQLの双方の変換で同じだろうと見做しています。より厳密さを求めるのであれば、DBMS_CRYPTO.HASHの利用も考慮する必要があります。パッケージDBMS_CRYPTOはデフォルトでは実行権限がなく、管理者によるGRANT文の実行が必要なためレポートに含めていません。


PL/SQLによるBLOBからBase64エンコードされたCLOBへの変換は、以下のコードで行なっています。Oracle APEXが提供しているAPEX_WEB_SERVICE.BLOB2CLOBBASE64を呼び出しています。

declare
C_THIS_PROCESS_NAME constant varchar2(80) := 'BLOB2BASE64_PLSQL';
l_blob blob;
l_base64 clob;
begin
/*
* アップロードされたファイルをBLOBとして取り出す。
*/
select blob_content into l_blob from apex_application_temp_files
where name = :P1_FILE;
/*
* PL/SQLによるBLOBからBASE64でエンコードしたCLOBへの変換には
* APEX_WEB_SERVICE.BLOB2CLOBBASE64を呼び出す。
*/
l_base64 := apex_web_service.blob2clobbase64(l_blob,'N','N');
/*
* 結果をAPEXコレクションにCLOBとして保存する。
*/
apex_collection.add_member(
p_collection_name => :P1_COLLECTION_NAME
,p_c001 => C_THIS_PROCESS_NAME
,p_clob001 => l_base64
);
end;

JavaScriptによるBLOBからBase64エンコードされたCLOBへの変換は、以下のコードで行なっています。Oracleが提供しているモジュールmle-encode-base64に含まれるファンクションencodeを呼び出しています。

const {encode: base64encode, decode: base64decode} = await import('mle-encode-base64');
const C_THIS_PROCESS_NAME = 'BLOB2BASE64_JS';
const collection_name = apex.env.P1_COLLECTION_NAME;
/*
* アップロードされたファイルをBLOBとして取り出す。
*/
const name = apex.env.P1_FILE;
const result = apex.conn.execute(
`select blob_content from apex_application_temp_files where name = :name`,
{
name: {
type: oracledb.DB_TYPE_VARCHAR,
dir: oracledb.BIND_IN,
val: name
}
},
{
fetchInfo: {
"BLOB_CONTENT": {
type: oracledb.ORACLE_BLOB
}
}
}
);
if (result.rows.length === 0) {
throw new Error(`No data found for name ${name}`);
}
else
{
// 検索結果は必ず1行のみ。
for (let row of result.rows) {
/*
* JavaScriptによるBLOBからBASE64でエンコードしたCLOBへの変換は
* モジュールmle-encode-base64に含まれているencodeを呼び出す。
*/
const blob = row.BLOB_CONTENT;
const base64string = base64encode(blob.getData());
/*
* 結果をAPEXコレクションにCLOBとして保存する。
*/
apex.conn.execute(
`begin apex_collection.add_member(p_collection_name => :col, p_c001 => :c001, p_clob001 => :clob001 ); end;`,
{
col: {
type: oracledb.DB_TYPE_VARCHAR,
dir: oracledb.BIND_IN,
val: collection_name
},
c001: {
type: oracledb.DB_TYPE_VARCHAR,
dir: oracledb.BIND_IN,
val: C_THIS_PROCESS_NAME
},
clob001: {
type: oracledb.DB_TYPE_CLOB,
dir: oracledb.BIND_IN,
val: base64string
}
}
);
};
};

PL/SQLによるBase64エンコードされたCLOBからBLOBへの変換は、以下のコードで行なっています。Oracle APEXが提供しているAPEX_WEB_SERVICE.CLOBBASE642BLOBを呼び出しています。

declare
C_THIS_PROCESS_NAME constant varchar2(80) := 'BASE642BLOB_PLSQL';
C_SOURCE_PROCESS_NAME constant varchar2(80) := 'BLOB2BASE64_JS';
l_blob blob;
l_base64 clob;
begin
/*
* JavaScriptでBase64に変換したCLOBを取り出す。
*/
select clob001 into l_base64 from apex_collections
where collection_name = :P1_COLLECTION_NAME and C001 = C_SOURCE_PROCESS_NAME;
/*
* PL/SQLによるBASE64でエンコードしたCLOBからBLOBへの変換には
* APEX_WEB_SERVICE.CLOBBASE642BLOBを呼び出す。
*/
l_blob := apex_web_service.clobbase642blob(l_base64);
/*
* 結果をAPEXコレクションにBLOBとして保存する。
*/
apex_collection.add_member(
p_collection_name => :P1_COLLECTION_NAME
,p_c001 => C_THIS_PROCESS_NAME
,p_blob001 => l_blob
);
end;

JavaScriptによるBase64エンコードされたCLOBからBLOBへの変換は、以下のコードで行なっています。Oracleが提供しているモジュールmle-encode-base64に含まれるファンクションdecodeを呼び出しています。

const {encode: base64encode, decode: base64decode} = await import('mle-encode-base64');
const C_THIS_PROCESS_NAME = 'BLOB2BASE64_JS';
const C_SOURCE_PROCESS_NAME = 'BLOB2BASE64_PLSQL';
const collection_name = apex.env.P1_COLLECTION_NAME;
/*
* PL/SQLでBase64に変換したCLOBを取り出す。
*/
const name = apex.env.P1_FILE;
const result = apex.conn.execute(
`select clob001 from apex_collections where collection_name = :collection_name and c001 = :c001`,
{
collection_name: {
type: oracledb.DB_TYPE_VARCHAR,
dir: oracledb.BIND_IN,
val: collection_name
},
c001: {
type: oracledb.DB_TYPE_VARCHAR,
dir: oracledb.BIND_IN,
val: C_SOURCE_PROCESS_NAME
}
},
{
fetchInfo: {
"CLOB001": {
type: oracledb.ORACLE_CLOB
}
}
}
);
if (result.rows.length === 0) {
throw new Error(`No data found for c001 ${c001}`);
}
else
{
// 検索結果は必ず1行のみ。
for (let row of result.rows) {
/*
* JavaScriptによるBASE64でエンコードしたCLOBからBLOBへの変換は
* モジュールmle-encode-base64に含まれているdecodeを呼び出す。
*/
const base64 = row.CLOB001;
const bytes = base64decode(base64.getData());
const blob = OracleBlob.createTemporary(false);
blob.open(OracleBlob.LOB_READWRITE);
blob.write(1, bytes);
/*
* 結果をAPEXコレクションにBLOBとして保存する。
*/
apex.conn.execute(
`begin apex_collection.add_member(p_collection_name => :col, p_c001 => :c001, p_blob001 => :blob001 ); end;`,
{
col: {
type: oracledb.DB_TYPE_VARCHAR,
dir: oracledb.BIND_IN,
val: collection_name
},
c001: {
type: oracledb.DB_TYPE_VARCHAR,
dir: oracledb.BIND_IN,
val: C_THIS_PROCESS_NAME
},
blob001: {
type: oracledb.DB_TYPE_BLOB,
dir: oracledb.BIND_IN,
val: blob
}
}
);
blob.close();
blob.freeTemporary();
};
};
今回の記事は以上になります。

Oracle APEXのアプリケーション作成の参考になれば幸いです。