Oracle Database 23aiよりMLE(Multilingual Engine)の機能が拡張され、MLEモジュールを作成できるようになりました。概ねESMAScript標準のモジュール(ES module)に相当します。
Oracle CorporationでMLE/JavaScriptのプロダクト・マネージャを務めるMartin Bachさんは、
彼のブログで、データベースのMLEおよび他の製品でもMLEに関連した機能について、いくつもの記事を公開されています。
本記事では、Martin Bachさんが紹介されているpdf-libを使った記事を参考にして、MLEモジュールのバンドル化を試します。また、SQL Developer Extension for VS Code 25.1から、MLE環境とMLEモジュールも扱えるようになったので、それらも使ってみます。
最初に作成したPDFを保存する表PDF_TESTを作成します。Martin Bachさんの元記事では主キー列IDとBLOB列Bがあるだけの表ですが、APEXのアプリケーションからダウンロードして、簡単に出力されたPDFを確認できるように列FILENAMEを追加します。
クイックSQLの以下のモデルより表PDF_TESTを作成します。
# pk: identity
pdf_test
filename vc80 /nn
b blob
レビューおよび実行をクリックします。
SQLスクリプトのスクリプト・エディタが開きます。スクリプト名にpdftestと入力し、実行をクリックします。即時実行するかどうか確認を求めるダイアログが開くので、即時実行をクリックします。
表PDF_TESTが作成されます。アプリケーションの作成をクリックします。デフォルトでは、作成した表のCRUD操作ができる、対話モード・レポートとフォームの画面を含むアプリケーションが作成されます。
アプリケーションの作成対象の表がPDF_TESTであることの確認を求められます。続けて、アプリケーションの作成をクリックします。
アプリケーション作成ウィザードが開きます。名前をSample PDF-LIBに変更し、アプリケーションの作成をクリックします。
アプリケーションが作成されます。表PDF_TESTに保存されているPDFを適切にダウンロードできるように、設定をいくつか変更します。
ページ・デザイナでページ番号2の対話モード・レポートのページを開きます。
対話モード・レポートの列Bを選択し、BLOB属性のファイル名列にFILENAMEを選択します。以上の変更で保存します。
PDFファイルはJavaScriptのコードを実行して作成するため、フォームからアップロードすることはありません。とはいえ、使わないにしても必要な設定はしておきます。
ページ・デザイナでページ番号3のフォームのページを開きます。ページ・アイテムP3_Bを選択し、ストレージのファイル名列にFILENAMEを指定します。
以上で表PDF_TESTに保存されたPDFファイルを、手元にダウンロードするAPEXアプリケーションが作成できました。
アプリケーション・ビルダーからインポートを開きます。
インポートするファイルとして、GitHubよりダウンロードしたmle-module-manager+ords-handler.zipを選択します。
次へ進みます。
アプリケーションのインストールを実行します。
以上でjsDelivrからESモジュールを取り出し、MLEモジュールを作成するアプリケーションがインポートできました。
APEXのワークスペース・スキーマにMLEでJavaScriptを実行する権限を与えます。Autonomous Databaseであれば管理者ユーザADMIN、Oracle Database 23ai Freeであれば、ユーザーSYSで以下のコマンドを実行します。
grant execute on javascript to [APEXワークスペース名];
grant execute dynamic mle to [APEXワークスペース名];
grant create mle to [APEXワークスペース名];
SQL> grant execute on javascript to wksp_apexdev;
Grantが正常に実行されました。
SQL> grant execute dynamic mle to wksp_apexdev;
Grantが正常に実行されました。
SQL> grant create mle to wksp_apexdev;
Grantが正常に実行されました。
SQL>
以上でインポートしたアプリケーションMLE Module Managerを実行します。
最初にボタンInitをクリックし、アプリケーションを初期化します。
Module Nameにpdf-libを入力し、ボタンAdd Es Moduleをクリックします。
jsDelivrからESモジュールとしてpdf-libを取得します。まだMLEモジュールは作成されていません。pdf-libが依存しているモジュールをすべて作成した後に、pdf-libをMLEモジュールとして作成します。
ボタンResolve Onceをクリックし、pdf-libが依存しているESモジュールを解析します。
モジュールpdf-libのStatusがRESOLVEDに変わり、pdf-libが依存しているモジュールがロードされます。
再度ボタンResolved Onceをクリックし、StatusがLOADEDになっているモジュールの依存モジュールを解析します。
pakoがLOADEDのまま残っています。
もう一度
Resolve Onceをクリックします。
pakoも
Statusが
RESOLVEDに変わり、
pdf-libの依存関係が解決されました。
すべての依存関係が解決される場合は多くはありません。モジュールfsなどMLEでサポートできないモジュールが含まれていると、そのモジュールの代替となる
ポリフィルが必要になります。
ボタンCreate Mle Modulesをクリックします。StatusがRESOLVEDのモジュールから、MLEモジュールを作成します。
正常にMLEモジュールが作成されると、StatusはVALIDに変わります。
MLE Envに
PDFWRITER_ENVを入力し、ボタン
Add Importsをクリックします。
MLE環境としてPDFWRITER_ENVを作成し、レポートに表示されているMLEモジュールをMLE環境に追加します。
以上でMLEで実行するJavaScriptのコードより、pdf-libを呼び出し可能になりました。
SQLコマンドを開き、ロードしたpdf-libを使ってPDFファイルを作成します。
言語にJavaScript(MLE)を選択し、環境としてPDFWRITER_ENVを選択します。
以下のコードを実行します。created by MLE/JavaScriptと記載されたPDFが表PDF_TESTに、ファイル名pdflib.pdfとして保存されます。
const { PDFDocument, StandardFonts, rgb } = await import('/npm/pdf-lib@1.17.1/+esm');
const pdfDoc = await PDFDocument.create();
const timesRoman = await pdfDoc.embedFont(StandardFonts.TimesRoman);
const page = pdfDoc.addPage();
const { _, height } = page.getSize();
const fontSize = 30;
page.drawText("created by MLE/JavaScript", {
x: 50,
y: height - 4 * fontSize,
size: 30,
font: timesRoman,
color: rgb(0, 0.53, 0.71),
});
const pdfBytes = await pdfDoc.save();
session.execute("insert into pdf_test (filename, b) values ('pdflib.pdf', :pdf)", [pdfBytes]);
先に作成したAPEXアプリケーションSample PDF-LIBより、pdflib.pdfをダウンロードして内容を確認します。
PDFの内容として以下が確認できます。pdf-libは適切に呼び出せています。
この記事では、pdf-libが依存しているライブラリをesbuildの実行により解決しています。
最初に作業ディレクトリとしてpdflib-testを作成します。
mkdir pdflib-test
cd pdflib-test
% mkdir pdflib-test
% cd pdflib-test
pdflib-test %
pdf-libとesbuildをインストールします。
npm install --save pdf-lib
npm install --save-exact --save-dev esbuild
pdflib-test % npm install --save pdf-lib
added 5 packages in 232ms
pdflib-test % npm install --save-exact --save-dev esbuild
added 2 packages, and audited 8 packages in 393ms
found 0 vulnerabilities
pdflib-test %
フォルダpdflib-testにフォルダsrcを作成し、ファイルpdfWriter.jsを作成します。
作成したファイルに以下の内容を記述します。元の記事で紹介されているpdfWriter.jsとほぼ同じですが、表PDF_TESTの列FILENAMEにesbuild.pdfと記入するように変更しています。
VS Codeの使用を想定しています。
import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
/**
* Creates a PDF document using JavaScript and saves it as a byte array before
* persisting it into a table. Relies on the "pdf-lib" library to do so.
*
* A faithful representation of https://pdf-lib.js.org/#create-document
*
* @async
* @returns {Promise<void>} A promise that resolves when the PDF document has been created and saved.
*/
export async function createPDFInJavaScript() {
const pdfDoc = await PDFDocument.create();
const timesRoman = await pdfDoc.embedFont(StandardFonts.TimesRoman);
const page = pdfDoc.addPage();
const { _, height } = page.getSize();
const fontSize = 30;
page.drawText("created by MLE/JavaScript", {
x: 50,
y: height - 4 * fontSize,
size: 30,
font: timesRoman,
color: rgb(0, 0.53, 0.71),
});
const pdfBytes = await pdfDoc.save();
// now persist the PDF as a BLOB in a table - see below for the
// table DDL statement
session.execute("insert into pdf_test(filenanme, b) values ('esbuild.pdf', :pdf)", [pdfBytes]);
}

ビルドします。
mkdir dist
npx esbuild src/pdfWriter.js --bundle --outfile=dist/bundle.js --format=esm
pdflib-test % mkdir dist
pdflib-test % npx esbuild src/pdfWriter.js --bundle --outfile=dist/bundle.js --format=esm
dist/bundle.js 801.0kb
⚡ Done in 12ms
pdflib-test %
SQL Developer Extension for VS Codeを開きます。
APEXのパーシング・スキーマへの接続を選択し、SQLclを開きます。
ターミナルに表示されるSQLclのプロンプトにて、フォルダpdflib-testに移動して、MLEモジュールを作成するコマンドを実行します。dist/bundle.jsからMLEモジュールpdfwriter_moduleが作成されます。
cd [フォルダpdflib-testへのパス/pdflib-test
mle create-module -filename dist/bundle.js -module-name pdfwriter_module -replace
選択しているAPEXのパーシング・スキーマへの接続の中に、JavaScriptモジュールのノードが含まれます(SQL Developer Extension for VS Code 25.1の新機能です)。その中にPDFWRITER_MODULEが含まれます。
内容はdist/bundle.jsなので、読んだり編集したりできるようなコードではありません。
作成したMLEモジュールPDFWRITER_MODULEを、すでに作成されているMLE環境PDFWRITER_ENVに含めます。
MLE環境のPDFWRITER_ENV上で右クリックしてコンテキスト・メニューを表示させ、Add Imports...を実行します。
Module Nameに"スキーマ名"."PDFWRITER_MODULE"を選択します。元記事に合わせてImport Nameはpdflibとして、適用をクリックします。
MLE環境PDFWRITER_ENVにインポート名pdflibとしてMLEモジュールPDFWRITER_MODULEが追加されました。
それでは、MLEモジュールPDFWRITER_MODULEに含まれるファンクションcreatePDFInJavaScriptを呼び出してみます。
SQLコマンドを開き、言語にJavaScript(MLE)を選択し、環境としてPDFWRITER_ENVを選択します。
以下のコードを実行します。
const { createPDFInJavaScript } = await import('pdflib');
await createPDFInJavaScript();
APEXアプリケーションSample PDF-LIBから、PDFファイルとしてesbuild.pdfが作成されていることを確認できます。
最後にSQLclのmleコマンドを使ってバンドル化してみます。参照した記事は「
SQLcl introduces support for Rollup.js to load MLE modules」です。
フォルダは新規にpdflib-test2を作成します。
mkdir pdflib-test2
cd pdflib-test2
% mkdir pdflib-test2
% cd pdflib-test2
pdflib-test2 %
pdf-libとrollup、@rollup/plugin-node-resolve、@rollup/plugin-commonjsをインストールします。
npm install --save pdf-lib
npm install --save-exact --save-dev rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs
pdflib-test2 % npm install --save pdf-lib
added 5 packages in 493ms
pdflib-test2 % npm install --save-exact --save-dev rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs
added 22 packages, and audited 28 packages in 2s
5 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
pdflib-test2 %
Martin Bachさんの記事では
Rollup Issue 1645のため、現時点でのRollupは不可となっています。Circular dependenciesが発生するのはモジュール
@pdf-lib/standard-fontsに関連しているようなので、
rollup.config.mjsの
externalに含めます。
import { nodeResolve } from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
export default {
input: 'src/pdfWriter.js',
output: {
format: 'esm'
},
plugins: [nodeResolve(), commonjs()],
external: ['mle-js-oracledb', 'oracledb','@pdf-lib/standard-fonts'],
};
ファイルrollup.config.mjsはフォルダpdflib-test2の直下に配置します。
esbuildの実行との違いが分かるように、pdfWriter.jsに含まれているファイル名をesbuild.pdfからrollup.pdfに変更します。フォルダsrcを作成し、その下にpdfWriter.jsを作成します。
APEXのワークスペース・スキーマへの接続でSQLclを開き、pdflib-test2へ移動して以下のコマンドを実行します。SQLclのmleコマンドでバンドル化を実行した上でMLEモジュールPDFWRITER_MODULE2を作成します。
mle create-module -bundler rollup -bundler-config rollup.config.mjs -filename src/pdfWriter.js -module-name PDFWRITER_MODULE2 -replace

JavaScriptモジュールとしてPDFWRITER_MODULE2が作成されていることを確認します。
先ほどと同様の手順でMLE環境PDFWRITER_ENVに今回作成したMLEモジュールPDFWRITER_MODULE2を含めます。Import Nameはpdflib2とします。
Circular dependenciesの発生を回避するためにexternalに設定したモジュールを@pdf-lib/standard-fontsとしてインポートできるようにします。
Module Nameに"ESM__PDF_LIB_STANDARD_FONTS_1_0_0"を選択し、Import Nameを@pdf-lib/standard-fontsとして適用します。
MLE環境PDFWRITER_ENVにImport Nameがpdflib2と@pdf-lib/standard-fontsが追加されていることを確認します。
MLEモジュールPDFWRITER_MODULE2に含まれるファンクションcreatePDFInJavaScriptを呼び出してみます。
SQLコマンドを開き、言語にJavaScript(MLE)を選択し、環境としてPDFWRITER_ENVを選択します。
以下のコードを実行します。
const { createPDFInJavaScript } = await import('pdflib2');
await createPDFInJavaScript();
APEXアプリケーションSample PDF-LIBから、PDFファイルとしてrollup.pdfが作成されていることを確認できます。
折角なので、APEXアプリケーションから上記の3種類のテスト・コードを呼び出せるようにしてみます。
APEXアプリケーションからMLEモジュールを呼び出すために、アプリケーション定義のセキュリティを開き、データベース・セッションのセクションに含まれるMLE環境にPDFWRITER_ENVを設定します。
変更の適用をクリックします。
対話モード・レポートのページに、外観のテンプレートにButtons Containerを選択した静的コンテンツのリージョンを作成します。そのリージョンにボタンNO_BUNDLER、ESBUILD、ROLLUPを作成します。動作のアクションはページの送信です。
ボタンNO_BUNDLERをクリックしたときに実行されるプロセスとしてNO_BUNDLERを作成します。
タイプはコードを実行、ソースの言語にJavaScript(MLE)を選択します。JavaScriptコードは動作確認の際にSQLコマンドで実行したコードをそのまま記述します。
サーバー側の条件のボタン押下時にNO_BUNDLERを指定します。
同様にボタンESBUILDをクリックしたときに実行されるプロセスとしてESBUILDを作成します。
ボタンROLLUPをクリックしたときに実行されるプロセスとしてROLLUPを作成します。
今回の記事は以上になります。
今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sample-pdf-lib.zip
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完