6 MLE JavaScript SQLドライバからのPL/SQLおよびSQLのコール

MLE JavaScript SQLドライバの概要

MLE JavaScriptドライバは、Node.js用のクライアント側Oracle SQLドライバであるnode-oracledbによく似たモデルとなっています。

サーバー側ドライバとクライアント側ドライバの間のこの密接な関係により、クライアント側のJavaScriptコードをNode.jsまたはDenoからデータベースに移植するために必要な労力が軽減されます。サーバー側の環境に適切にマップできない機能は、MLEおよびMLE JavaScriptドライバでは除外され、エラーをスローします。

これは、コードのどの部分に変更が必要かを識別するために役立ちます。さらに、MLE JavaScript実装は純粋なJavaScript実装です。ウィンドウ・オブジェクトおよびファイルとネットワークの直接I/Oなど、ECMAScript標準に含まれない特定の機能はMLEでは使用できません。

mle-js-oracledb SQLドライバは同期操作モデルにデフォルト設定され、async/awaitによる非同期実行を部分的にサポートしています。

ノート:

本番コードはエラー処理およびロギングに関する業界のベスト・プラクティスに準拠している必要がありますが、この章の例ではわかりやすくするために省略されています。また、読みやすいことから、ほとんどの例で同期実行モデルを採用しています。

関連項目:

トピック

MLE JavaScriptドライバの操作

MLE JavaScriptドライバを操作するための汎用的なワークフロー。

高レベルでは、MLE JavaScriptドライバの操作は、クライアント側のnode-oracledbドライバの使用によく似ています。つまり、次のようになります。

  1. 既存のデータベース・セッションへの接続ハンドルを取得します。
  2. その接続を使用してSQL文を実行します。
  3. 実行された文によって戻された結果オブジェクトと、発生した可能性のあるデータベース・エラーを確認します。
  4. select文の場合は、結果のカーソルを反復処理します。
  5. データを操作する文については、トランザクションをコミットするかロールバックするかを決定します。

クライアント側のNode.jsまたはDenoから移植されていないアプリケーションでは、グローバル・スコープで使用可能な頻繁に使用される多数の変数など、MLE JavaScript SQLドライバで使用可能なコーディング支援のメリットが得られます。これらの変数には、次のものが含まれます。

  • OracleDbドライバ・オブジェクトのoracledb
  • デフォルトの接続オブジェクトのsession
  • SodaDatabaseオブジェクトのsoda

さらに、次の型も使用できます。

  • OracleNumber
  • OracleClob
  • OracleBlob
  • OracleTimestamp
  • OracleTimestampTZ
  • OracleDate

これらのオブジェクトをグローバル・スコープで使用できるため、ボイラープレート・コードを記述する必要性が少なくなります。MLE JavaScript SQLドライバで使用可能なグローバル・シンボルの詳細は、サーバー側のJavaScript APIドキュメントを参照してください。

MLE JavaScriptドライバの接続管理

MLE JavaScriptドライバの接続管理を処理する際の考慮事項。

MLE JavaScriptドライバの接続管理は、クライアント・ドライバと比較して大幅に簡素化されています。JavaScriptストアド・プロシージャが起動されると、データベース・セッションがすでに存在するため、接続、接続プールおよびセキュアな資格証明管理の確立や破棄その他多くのことについて心配する必要はありません。

mle-js-oracledbモジュールのgetDefaultConnection()メソッドに注意するか、グローバル・セッション・オブジェクトを使用することで済みます。

SQL文の実行の概要

Connectionクラスのexecute()メソッドによって、単一のSQLまたはPL/SQL文を実行できます。問合せ結果は、単一のJavaScript配列で戻すことも、ResultSetオブジェクトを使用してバッチでフェッチすることもできます。

ResultSetとしてフェッチすると、フェッチ操作をより詳細に制御できるのに対し、配列を使用すると、必要なコード行数が減り、戻されるデータ量が非常に多い場合を除き、パフォーマンス上の利点があります。

例6-1 MLE JavaScript SQLドライバの開始

次のコードは、MLE JavaScript SQLドライバを現行のモジュールのネームスペースにインポートする方法を示しています。この例は、node-oracledbのドキュメントNode.jsのSQL SELECT文に記載されている例に基づいています。

CREATE OR REPLACE MLE MODULE js_sql_mod LANGUAGE JAVASCRIPT AS

import oracledb from "mle-js-oracledb";

/**
 * Perform a lookup operation on the HR.DEPARTMENTS table to find all
 * departments managed by a given manager ID and print the result on
 * the console
 * @param {number} managerID the manager ID
*/

function queryExample(managerID) {

  if (managerID === undefined) {
    throw new Error (
        "Parameter managerID has not been provided to queryExample()"
    );
  }
  let connection;

  try {
    connection = oracledb.defaultConnection();

    const result = connection.execute(`
        SELECT manager_id, department_id, department_name
        FROM hr.departments
        WHERE manager_id = :id`,
        [
            managerID
        ], 
        {
            outFormat: oracledb.OUT_FORMAT_OBJECT
        }
    );
    if (result.rows.length > 0) {
        for (let row of result.rows) {
            console.log(`The query found a row:
                manager_id:      ${row.MANAGER_ID}
                department_id:   ${row.DEPARTMENT_ID}
                department_name: ${row.DEPARTMENT_NAME}`);
        }
    } else {
        console.log(`no data found for manager ID ${managerID}`);
    }

  } catch (err) {
    console.error(`an error occurred while processing the query: ${err.message}`);
  } 
}

export { queryExample };
/

モジュールに存在する唯一のファンクションであるqueryExample()は、connection.execute()をコールすることにより、バインド変数を使用してHR部門表から単一の行を選択します。バインド変数の値は、パラメータとしてファンクションに渡されます。connection.execute()に渡されるもう1つのパラメータは、問合せによって戻される各行をJavaScriptオブジェクトとして指定する必要があることを示します。

指定されたmanagerIDのデータが見つかった場合は、画面に出力されます。デフォルトでは、console.log()へのコールはDBMS_OUTPUTにリダイレクトされます。戻された行がない場合は、この事実を示すメッセージがコンソールに出力されます。

次のスニペットのコール仕様を使用すると、データベースでコードを起動できます。

CREATE OR REPLACE PROCEDURE p_js_sql_query_ex(
    p_manager_id number)
AS MLE MODULE js_sql_mod
SIGNATURE 'queryExample(number)';
/

デフォルトのままになっている場合、p_js_sql_query_exを起動すると、次のように表示されます。

SQL> set serveroutput on
SQL> EXEC p_js_sql_query_ex(103)
The query found a row:
manager_id:      103
department_id:   60
department_name: IT

関連項目:

mle-js-oracledbなどの組込みJavaScriptモジュールの詳細は、サーバー側JavaScript APIのドキュメントを参照してください

例6-2 グローバル変数を使用したSQL実行の簡略化

例6-1は、MLEで使用するために大幅に簡略化できます。グローバル・スコープに挿入された変数を参照できるため、mle-js-oracledbモジュールをインポートする必要がなくなります。さらに、モジュールには単一のファンクションしか定義されないため、インライン・コール仕様により入力がさらに節約されます。

CREATE OR REPLACE PROCEDURE js_sql_mod_simplified(
    "managerID" number
) AS MLE LANGUAGE JAVASCRIPT
q'~
if (managerID === undefined || managerID === null){
    throw new Error (
        "Parameter managerID has not been provided to js_sql_mod_simplified()"
    );
}

const result = session.execute(`
    SELECT
        manager_id,
        department_id,
        department_name
    FROM
        hr.departments
    WHERE
        manager_id = :id`,
    [ managerID ]
);

if(result.rows.length > 0){
    for(let row of result.rows){
        console.log(
            `The query found a row:
             manager_id: ${row.MANAGER_ID}
             department_id: ${row.DEPARTMENT_ID}
             department_name: ${row.DEPARTMENT_NAME}`
        );
    }
} else {
    console.log(`no data found for manager ID ${managerID}`);
}
~';
/
js_sql_mod_simplifiedを起動すると、次のように出力されます:
SQL> set serveroutput on
SQL> exec js_sql_mod_simplified(100);

The query found a row:
manager_id:      100
department_id:   90
department_name: Executive

node-oracledbとmle-js-oracledbとの処理の比較

node-oracledbのドキュメントでは、async/awaitインタフェースの使用が推奨されています。クライアントとサーバーの対話の性質により、nodeとデータベースの間に含まれる処理の大部分が非同期的に実行されます。

MLE JavaScript SQLドライバは非同期処理を必要としません。PL/SQLドライバと同様に、これはデータベース内のドライバの場所によるものです。MLE JavaScript SQLドライバはasync/await構文を理解しますが、リクエストは内部で同期的に処理されます。

node-oracledbドライバとは異なり、MLE JavaScript SQLドライバは、ECMAScript 2022構文を使用すると、配列(oracledb.OUTFORMAT_ARRAY)ではなくオブジェクト(oracledb.OUT_FORMAT_OBJECT)として行を戻します。非推奨のrequire構文に依存しているコードは、行を配列として戻すことで下位互換性が維持されます。

ノート:

promiseベースのインタフェースは、MLE JavaScriptドライバには用意されていません。

MLE JavaScriptドライバを使用したデータの選択

直接フェッチまたはResultSetオブジェクトを使用してデータを選択できます。

出力形式としては配列とオブジェクトのどちらかを選択できます。デフォルトでは、JavaScriptオブジェクトを使用してデータが直接フェッチで戻されます。

トピック

直接フェッチ: 配列

直接フェッチは、MLE JavaScriptドライバのデフォルトです。

直接フェッチでは、問合せ結果がresult.rowsで提供されます。outFormatoracledb.OUT_FORMAT_ARRAYとして指定した場合、これは多次元JavaScript配列です。行の反復処理では、select文内の位置に基づいて列にアクセスできます。select文内の列の順序を変更するには、出力の解析の変更が必要です。これにより、検出が困難なバグが発生する可能性があるため、MLE JavaScript SQLドライバは、配列ではなく、デフォルトでオブジェクト(oracledb.OUT_FORMAT_OBJECT)を戻します。

例6-3に、同期実行モデルを使用した直接フェッチを示します。

例6-3 直接フェッチを使用したデータの選択: 配列

CREATE OR REPLACE PROCEDURE dir_fetch_arr_proc
AS MLE LANGUAGE JAVASCRIPT
q'~
const result = session.execute(
    `SELECT
        department_id,
        department_name
    FROM
        hr.departments
    FETCH FIRST 5 ROWS ONLY`,
    [],
    {
        outFormat: oracledb.OUT_FORMAT_ARRAY
    }
);
for (let row of result.rows) {
    const deptID = String(row[0]).padStart(3, '0');
    const deptName = row[1];
    console.log(`department ID: ${deptID} - department name: ${deptName}`);
}
~';
/

BEGIN 
    dir_fetch_arr_proc;
END;
/

結果:

department ID: 010 - department name: Administration
department ID: 020 - department name: Marketing
department ID: 030 - department name: Purchasing
department ID: 040 - department name: Human Resources
department ID: 050 - department name: Shipping

execute()ファンクションは、resultオブジェクトを戻します。文のタイプ(select、insert、deleteなど)に応じて、さらに処理するために様々なプロパティを使用できます。

mle-js-oracledbの詳細は、サーバー側JavaScript APIのドキュメントを参照してください。

直接フェッチ: オブジェクト

直接フェッチを使用すると、デフォルトでJavaScriptオブジェクトが戻されます。

selectリスト内の列の順序に関する潜在的な問題に対処するために、結果は配列ではなくJavaScriptオブジェクトとして戻されます。

例6-4 直接フェッチを使用したデータの選択: オブジェクト

CREATE OR REPLACE PROCEDURE dir_fetch_obj_proc
AS MLE LANGUAGE JAVASCRIPT 
q'~
const result = session.execute(
    `SELECT
        department_id,
        department_name
    FROM
        hr.departments
    FETCH FIRST 5 ROWS ONLY`,
    [],
    { outFormat: oracledb.OUT_FORMAT_OBJECT }
);

for (let row of result.rows) {
    const deptID = String(row.DEPARTMENT_ID).padStart(3, '0');
    const deptName = row.DEPARTMENT_NAME;
    console.log(`department ID: ${deptID} - department name: ${deptName}`);
}
~';
/

BEGIN
    dir_fetch_obj_proc();
END;
/

結果:

department ID: 010 - department name: Administration
department ID: 020 - department name: Marketing
department ID: 030 - department name: Purchasing
department ID: 040 - department name: Human Resources
department ID: 050 - department name: Shipping

PL/SQLとは異なり、JavaScriptでは名前付きパラメータという概念がサポートされていません。execute()メソッドは、SQL文、bindParamsおよびオプションをそのとおりの順序で受け入れます。問合せではバインド変数が使用されないため、空の配列はファンクションのシグネチャと一致します。

関連項目:

mle-js-oracledb組込みモジュールの詳細は、サーバー側JavaScript APIのドキュメントを参照してください

ResultSetとしての行のフェッチ: 配列

直接フェッチを使用するかわりに、ResultSetオブジェクトを使用できます。

直接フェッチの使用に加えて、ResultSetオブジェクトを使用することもできます。オプション・プロパティresultSetがtrueに設定されている場合、ResultSetが作成されます。ResultSetの行は、getRow()またはgetRows()を使用してフェッチできます。

デフォルトでは行は配列ではなくJavaScriptオブジェクトとしてフェッチされるため、行をResultSetとしてフェッチするには、outFormatoracledb.OUT_FORMAT_ARRAYとして定義する必要があります。

例6-5 ResultSetを使用した行のフェッチ

CREATE OR REPLACE PROCEDURE dir_fetch_rs_arr_proc
AS MLE LANGAUGE JAVASCRIPT
q'~
const result = session.execute(
    `SELECT
        department_id,
        department_name
    FROM
        hr.departments
    FETCH FIRST 5 ROWS ONLY`,
    [],
    {
        resultSet: true,
        outFormat: oracledb.OUT_FORMAT_ARRAY
    }
);

const rs = result.resultSet;
let row;
while ((row = rs.getRow())){
    const deptID = String(row[0]).padStart(3, '0');
    const deptName = row[1];
    console.log(`department ID: ${deptID} - department name: ${deptName}`);
}
rs.close();
~';
/

フェッチ操作では、オブジェクトではなく配列が明確にリクエストされたことに注意してください。デフォルトではオブジェクトが戻されます。

EXEC dir_fetch_rs_arr_proc();

結果:

department ID: 010 - department name: Administration
department ID: 020 - department name: Marketing
department ID: 030 - department name: Purchasing
department ID: 040 - department name: Human Resources
department ID: 050 - department name: Shipping

ResultSetとしての行のフェッチ: ResultSetオブジェクトの反復処理

ResultSet.getRow()およびResultSet.getRows()ファンクションに加えて、MLE JavaScriptドライバのResultSetは、ResultSetを反復処理するプロセスを簡略化するiterableプロトコルとiteratorプロトコルを実装します。

iterableプロトコルまたはiteratorプロトコルを使用できます。どちらも、ResultSetの操作を大幅に簡略化します。それぞれのオプションを例6-6例6-7に示します。

ノート:

ResultSetオブジェクトが不要になったら、クローズする必要があります。

例6-6 ResultSetでのiterableプロトコルの使用

この例では、ResultSet.getRow()のかわりにiterableプロトコルを使用する方法を示します。列の値の配列を提供するのではなく、かわりにJavaScriptオブジェクトが戻されます。

CREATE OR REPLACE PROCEDURE rs_iterable_proc
AS MLE LANGAUGE JAVASCRIPT
q'~
const result = session.execute(
    `SELECT
        department_id,
        department_name,
    FROM
        hr.departments
    FETCH FIRST 5 ROWS ONLY`,
    [],
    {
        resultSet: true;
    }
);
const rs = result.resultSet;
for (let row of rs){
    const deptID = String(row.DEPARTMENT_ID).padStart(3, '0');
    const deptName = row.DEPARTMENT_NAME;
    console.log(`department ID: ${deptID} - department name: ${deptName}`);
}
rs.close();
~';
/

BEGIN
    rs_iterable_proc;
END;
/

結果:

department ID: 010 - department name: Administration
department ID: 020 - department name: Marketing
department ID: 030 - department name: Purchasing
department ID: 040 - department name: Human Resources
department ID: 050 - department name: Shipping

例6-7 ResultSetでのiteratorプロトコルの使用

この例では、iteratorプロトコルを使用してResultSetを反復処理する方法を示します。これは、例6-6の再実装です。

CREATE OR REPLACE PROCEDURE rs_iterator_proc
AS MLE LANGUAGE JAVASCRIPT
q'~
const result = session.execute(
    `SELECT 
        department_id,
        department_name
    FROM
        hr.departments
    FETCH FIRST 5 ROWS ONLY`,
    [],
    {
        resultSet: true
    }
);
const iterator = result.resultSet.iterator();
let row = iterator.next();
while(! row.done){
    const deptID = String(row.value.DEPARTMENT_ID).padStart(3, '0');
    const deptName = row.value.DEPARTMENT_NAME;
    console.log(`department ID: ${deptID} - department name: ${deptName}`);
    row.iterator.next();
}

result.resultSet.close();
~';
/

EXEC rs_iterator_proc();

結果:

department ID: 010 - department name: Administration
department ID: 020 - department name: Marketing
department ID: 030 - department name: Purchasing
department ID: 040 - department name: Human Resources
department ID: 050 - department name: Shipping

データ変更

MLE JavaScript SQLドライバを使用してデータを変更します。

データの選択に加えて、MLE JavaScript SQLドライバを使用してデータの挿入、更新、削除およびマージを行うこともできます。これらの操作には、データを選択するときに使用するのと同じ一般的なワークフローを適用できます。

例6-8 MLE JavaScript SQLドライバを使用した行の更新

CREATE OR REPLACE MLE MODULE row_update_mod LANGUAGE JAVASCRIPT AS 
import oracledb from "mle-js-oracledb"; 
export function updateCommissionExampleEmpID145() { 
    const conn = oracledb.defaultConnection(); 
    const result = conn.execute( 
        `UPDATE employees 
         SET commission_pct = commission_pct * 1.1  
         WHERE employee_id = 145`
    ); 
    return result.rowsAffected; 
} 
/ 

resultオブジェクトのrowsAffectedプロパティを照会して、更新の影響を受けた行の数を確認できます。JavaScriptファンクションupdateCommissionExampleEmpID145()は、影響を受けた行の数をコール元に戻します。この場合、ファンクションは1を戻します。

データを更新する別の方法は、connection.executeMany()メソッドを使用することです。このファンクションは、バインド変数とともに使用すると最も効果的です。

バインド変数

バインド変数を使用して、データベースに渡されるデータまたはデータベースから取得されるデータを制御します。

SQLおよびPL/SQL文には、接頭辞としてコロンが付いた識別子で示されるバインド変数を含めることができます。これらのパラメータは、別個に指定された値が実行時に代入される文内の場所、または実行後に値が戻される場所を示します。

Oracle Databaseには、次の3種類のバインド変数があります:
  • INバインド変数
  • OUTバインド変数
  • IN OUTバインド変数

INバインドは、データベースに渡される値です。OUTバインドは、データベースからデータを取得するために使用されます。IN OUTバインドは渡され、文の実行後に異なる値を戻すことができます。

文字列連結やテンプレート・リテラルを使用してSQLまたはPL/SQL文を構成するよりも、バインド変数を使用することをお薦めします。バインド変数を使用すると、パフォーマンスとセキュリティの両面で有益である可能性があります。バインド変数を使用すると、Oracle Databaseでは、リソースと時間を消費するハード解析操作を実行する必要がありません。かわりに、カーソル・キャッシュにすでに存在するカーソルを再利用できます。

ノート:

バインド変数は、CREATE TABLEなどのDDL文で使用することも、問合せのテキストを代入することもできません。データのみを代入できます。

トピック

名前によるバインドと位置によるバインドの使用

バインド変数は、名前による方法と位置による方法の2つの方法で使用されます。これらのオプションは相互に排他的であるため、指定されたSQLコマンドについて一方を選択する必要があります。

トピック

名前付きバインド変数
名前によってバインドするには、バインド変数が、接頭辞としてコロンが付いた文字列リテラルである必要があります。
名前付きバインドの場合、connection.execute()ファンクションのbindParams引数には、定義される各バインド変数の次のプロパティを指定することをお薦めします。
プロパティ 説明
dir バインド変数の方向
val SQL文に渡される値
type データ型

例6-9 名前付きバインド変数の使用

CREATE OR REPLACE PROCEDURE named_binds_ex_proc(
    "deptName" VARCHAR2,
    "sal" NUMBER
)
AS MLE LANGUAGE JAVASCRIPT
q'~
if (deptName === null || sal === null){
    throw new Error(
        `must provide deptName and sal to named_binds_ex_proc()`
    );
}

const result = session.execute(
    `SELECT
        e.first_name ||
        ''    ||
        e.last_name employee_name,
        e.salary
    FROM
        hr.employees e
        LEFT JOIN hr.departments d ON (e.department_id = d.department_id)
    WHERE
        nvl(d.department_name, 'n/a') = :deptName
        AND salary > :sal
    ORDER BY
        e.employee_id`,
    {
        deptName:{
            dir: oracledb.BIND_IN,
            val: deptName,
            type: oracledb.STRING
        },
        sal:{
            dir: oracledb.BIND_IN,
            val: sal,
            type: oracledb.NUMBER
        }
    }
);
console.log(`Listing employees working in ${deptName} with a salary > ${sal}`);
for (let row of result.rows){
    console.log(`${row.EMPLOYEE_NAME.padEnd(25)} - ${row.SALARY}`);
}
~';
/
connection.execute()bindParams引数は、次の2つの名前付きバインド・パラメータを定義します:
  • deptName
  • sal

この例では、ファンクションの入力パラメータがバインド変数の名前と一致していることで読みやすくなっていますが、このことは必須ではありません。bindParamsのマッピングが正しいかぎり、バインド変数名を割り当てることができます。

位置指定バインド変数
名前付きバインド・パラメータを使用するかわりに、バインド変数情報を配列として指定することもできます。

配列内の要素の数は、SQLテキスト内のバインド・パラメータの数と一致している必要があります。名前によってマッピングするのではなく、バインド変数と値のマッピングは、テキスト内のバインド変数の位置とバインド配列内の項目の位置に基づきます。

例6-10 位置指定バインド変数の使用

この例は、位置指定バインド変数の使用を示し、例6-9の再実装を表しています

CREATE OR REPLACE PROCEDURE positional_binds_ex_proc(
    "deptName" VARCHAR2,
    "sal" NUMBER,
)
AS MLE LANGUAGE JAVASCRIPT
q'~
if (deptName === null || sal === null){
    thow new Error(
        `must provide deptName and sal to positional_binds_ex_proc()`
    );
}

const result = session.execute(
    `SELECT
        e.first_name ||
        ''    ||
        e.last_name employee_name,
        e.salary
    FROM
        hr.employees e
        LEFT JOIN hr.departments d ON (e.department_id = d.department_id)
    WHERE
        nvl(d.department_name, 'n/a') = :deptName
        AND salary > :sal
    ORDER BY
        e.employee_id`,
    [
        deptName, 
        sal
    ]
);
console.log(`Listing employees working in ${deptName} with a salary > ${sal}`);
for(let row of result.rows){
    console.log(`${row.EMPLOYEE_NAME.padEnd(25)} - ${row.SALARY}`);
}
~';
/

この例では、bindParamsはオブジェクトではなく、配列です。SQLテキスト内のバインド変数と値の間のマッピングは、位置によって行われます。bindParams配列内の最初の項目は、SQLテキスト内のプレースホルダの最初の出現にマップされ、その後も同様にマップされます。

RETURNING INTO句

RETURNING INTO句の使用について説明します。
RETURNING INTO句を使用すると、次のことができます
  • 更新中に変更された値をフェッチする
  • 単一行の挿入操作中に自動生成されたキーを戻す
  • 削除された行をリストする

例6-11 RETURNING INTO句の使用

この例では、更新操作後に古い値と新しい値を取得する方法を示します。これらの値を使用して、さらに処理を行うことができます。

CREATE OR REPLACE PROCEDURE ret_into_ex_proc(
    "firstEmpID" NUMBER,
    "lastEmpID" NUMBER
)
AS MLE LANGUAGE JAVASCRIPT
q'~
if (firstEmpID === null || lastEmpID === null){
    throw new Error(
        `must provide deptName and sal to ret_into_ex_proc()`
    );
}

const result = session.execute(
    `UPDATE
        hr.employees
    SET
        last_name = upper(last_name)
    WHERE
        employee_id between :firstEmpID and :lastEmpID
    RETURNING
        old last_name
        new last_name
    INTO
        :oldLastName,
        :newLastName`,
    {
        firstEmpID: {
            dir: oracledb.BIND_IN,
            val: firstEmpID,
            type: oracledb.NUMBER
        },
        lastEmpID: {
            dir: oracledb.BIND_IN,
            val: lastEmpID,
            type: oracledb.NUMBER
        },
        oldLastName: {
            type: oracledb.STRING,
            dir: oracledb.BIND_OUT
        },
        newLastName: {
            type: oracledb.STRING,
            dir: oracledb.BIND_OUT
        }
    }
);

if (result.rowsAffected > 1){
    console.log(
        `update() completed successfully:
        - old values: ${JSON.stringify(result.outBinds.oldLastName)}
        - new values: ${JSON.stringify(result.outBinds.newLastName)}`
    );
} else {
    throw new Error(
        `found no row to update in range ${firstEmpID} to ${lastEmpID}`
    );
}
~';
/
この例では、INOUTの両方のバインド変数を扱っています:
  • firstEmpIDおよびlastEmpIDでは、更新するデータ範囲を指定します
  • oldLastNameは、更新前の姓をすべて含む配列です
  • newLastNameは、新しい値を含む別の配列です

バッチ操作

connection.execute()ファンクションをコールすることに加えて、connection.executeMany()を使用してバッチ操作を実行することもできます。

connection.executeMany()を使用することはconnection.execute()を複数回コールすることと似ていますが、必要な作業が少なくなります。これは、複数の行を挿入または更新するときなど、バッチ変更を処理するための効率的な方法です。connection.executeMany()メソッドを問合せに使用することはできません。

connection.execute()には、SQL文で処理する変数を含む配列が必要です。例6-12bindData配列には、SQL文で定義されたバインド変数ごとに1つずつ、複数のJavaScriptオブジェクトが含まれます。forループによって、それらのオブジェクトが構成され、bindData配列に追加されます。

バッチ操作に渡される値に加えて、MLE JavaScript SQLドライバは値のデータ型を把握する必要があります。この情報は、connection.executeMany()のオプション・パラメータでbindDefsプロパティとして渡されます。例6-12の古い姓と新しい姓はどちらも、日付として定義されたchangeDateを持つ文字列です。

connection.execute()ファンクションと同様に、connection.executeMany()rowsAffectedプロパティを戻すため、バッチ処理された行の数を簡単に識別できます。

例6-12 バッチ操作の実行

この例では古い姓と新しい姓を監査表に挿入して例6-10を拡張しています。

CREATE OR REPLACE PROCEDURE ret_into_audit_ex_proc(
    "firstEmpID" NUMBER,
    "lastEmpID" NUMBER
)
AS MLE LANGUAGE JAVASCRIPT
q'~
if (firstEmpID === null || lastEmpID === null){
    throw new Error(
        `must provide deptName and sal to ret_into_audit_ex_proc()`
    );
}

let result = session.execute(
    `UPDATE
        hr.employees
    SET
        last_name = upper(last_name)
    WHERE
        employee_id between :firstEmpID and :lastEmpID
    RETURNING
        old last_name,
        new last_name
    INTO
        :oldLastName,
        :newLastName`,
    {
        firstEmpID: {
            dir: oracledb.BIND_IN,
            val: firstEmpID,
            type: oracledb.NUMBER
        },
        lastEmpID: {
            dir: oracledb.BIND_IN,
            val: lastEmpID,
            type: oracledb.NUMBER
        },
        oldLastName: {
            type: oracledb.STRING,
            dir: oracledb.BIND_OUT
        };
        newLastName: {
            type: oracledb.STRING,
            dir: oracledb.BIND_OUT
        }
    }
);

if (result.rowsAffected > 1){
    // store the old data and new values in an audit table
    let bindData = [];
    const changeDate = new Date();
    for (let i = 0; i < result.outBinds.oldLastName.length, i++){
        bindDate.push(
            {
                oldLastName: result.outBinds.oldLastName[i],
                newLastName: result.outBinds.newLastName[i],
                changeDate: changeDate
            }
        );
    }
    // use executeMany() with the newly populated array
    result = session.executeMany(
        `insert into EMPLOYEES_AUDIT_OPERATIONS(
            old_last_name,
            new_last_name,
            change_date
        ) values (
            :oldLastName,
            :newLastName,
            :changeDate
        )`,
        bindData,
        {
            bindDefs: {
                oldLastName: {type: oracledb.STRING, maxSize: 30},
                newLastName: {type: oracledb.STRING, maxSize: 30},
                changeDate: {type: oracledb.DATE}
            }
        }
    );

} else {
    throw new Error(
        `found no row to update in range ${firstEmpID} to ${lastEmpID}`
    );
}
~';
/

初期更新文が完了すると、データベースは、更新の影響を受けたlast_name列の古い値と新しい値をresultオブジェクトのoutBindsプロパティで提供します。oldLastNamenewLastNameはどちらも配列です。配列の長さは、更新された行の数を表します。

MLE JavaScript SQLドライバからのPL/SQLの起動

MLE JavaScriptドライバを使用して、PL/SQLからファンクションおよびプロシージャをコールします。

Oracle DatabaseのAPIの大部分は、PL/SQLで提供されます。これは問題ではありません。JavaScriptからPL/SQLを簡単にコールできます。MLE JavaScript SQLドライバを使用したPL/SQLの起動は、SQL文のコールに似ています。

例6-13 JavaScriptからのPL/SQLのコール

CREATE OR REPLACE MLE MODULE plsql_js_mod
LANGUAGE JAVASCRIPT AS
/**
 * Read the current values for module and action and return them as
 * a JavaScript object. Typically set before processing starts to
 * allow you to restore the values if needed.
 * @returns an object containing module and action
 */
function preserveModuleAction(){
    //Preserve old module and action. DBMS_APPLICATION_INFO provides
    // current module and action as OUT binds
    let result = session.execute(
        `BEGIN
            DBMS_APPLICATION_INFO.READ_MODULE(
                :l_module,
                :l_action
            );
        END;`,
        {
            l_module: {
                dir: oracledb.BIND_OUT,
                type: oracledb.STRING
            },
            l_action: {
                dir: oracledb.BIND_OUT,
                type: oracledb.STRING
            }
        }
    );
    
    // Their value can be assigned to JavaScript variables
    const currentModule = result.outBinds.l_module;
    const currentAction = result.outBinds.l_action;

    // ... and returned to the caller
    return {
        module: currentModule,
        action: currentAction
    }
}

/**
 * Set module and action using DBMS_APPLICATION_INFO
 * @param theModule the module name to set
 * @param theAction the name of the action to set
 */
function setModuleAction(theModule, theAction){
    session.execute(
        `BEGIN
            DBMS_APPLICATION_INFO.SET_MODULE(
                :module,
                :action
            );
        END;`,
        [
            theModule,
            theAction
        ]
    );
}

/**
 * The only public function in this module simulates some heavy
 * processing for which module and action are set using the built-in
 * DBMS_APPLICATION_INFO package.
 */
export function plsqlExample(){
    // preserve the values for module and action before we begin
    const moduleAction = preserveModuleAction();

    // set the new values to reflect the function's execution
    // within the module
    setModuleAction(
        'plsql_js_mod',
        'plsqlExample()'
    )

    // Simulate some intensive processing... While this is ongoing
    // module and action in v$session should have changed to the 
    // values set earlier. You can check using 
    // SELECT module, action FROM v$session WHERE module = 'plsql_js_mod'
    session.execute(
        `BEGIN
            DBMS_SESSION.SLEEP(60);
        END;`
    );

    // and finally reset the values to what they were before
    setModuleAction(
        moduleAction.module,
        moduleAction.action
    );
}
/

この例は、以前の例より少し詳しく説明されており、共通機能を独自の(プライベート)ファンクションに分割しています。OUT変数の使用は、DBMS_APPLICATION_INFOへのpreserveModuleAction()のコールで確認できます。値は、result.outBindsを使用して取得できます。

モジュールおよびアクションの現在の値をローカル変数に格納した後、追加の匿名PL/SQLブロックが起動され、複雑なデータ処理をシミュレートする60秒のスリープ・サイクルに入る前に、まずモジュールおよびアクションが設定されます。シミュレートされたデータ処理ルーチンが終了すると、名前付きINバインド変数を使用して、モジュールとアクションが元の値にリセットされます。バインド変数を使用する方が、文字列連結よりも安全です。

モジュールとアクションを設定することは、進行中のアクティビティをデータベースに通知する有効な手段であり、これにより、パフォーマンス・レポートでアクティビティをグループ化しやすくなります。

SQL文のエラー処理

JavaScriptには、Javaのような例外フレームワークが用意されています。node-oracledbのようにErrorオブジェクトをpromiseまたはコールバックとして戻すのではなく、MLE JavaScriptドライバはエラーのスローを使用します。この概念は、PL/SQL開発者にとって非常になじみ深いものです。

JavaScriptコードでのtry-catch-finallyの使用は、PL/SQL開発者がbegin-exception-endブロックを使用して処理中にエラーを検出する方法に似ています。

例外を再スローする必要がある場合は、JavaScriptのthrow()コマンドを使用します。これにより、catchブロックで処理された後にエラーがスタックをバブルアップします。例6-15に、この概念を示します。

例6-14 JavaScriptファンクションの内部でのSQLエラー処理

CREATE OR REPLACE MLE MODULE js_err_handle_mod
LANGUAGE JAVASCRIPT AS

/**
 *short demo showing how to use try/catch to catch an error
 *and proceeding normally. In the example, the error is 
 *provoked
*/
export function errorHandlingDemo(){

    try{
        const result = session.execute(
            `INSERT INTO
                surelyThisTableDoesNotExist
            VALUES
                (1)`
        );

    console.log(`there were ${result.rowsAffected} rows inserted`);

    } catch(err) {
        logError('this is some message', err);

        //tell the caller that something went wrong
        return false;
    }

    //further processing

    //return successful completion of the code
    return true;
}

/**
 *log an error using the fictional "logging_pkg". Think of it as
 *a package logging errors in a framework for later analysis.
 *@param msg an accompanying message
 *@param err the error encountered
*/
function logError(msg, err){
    const result = session.execute(
        `BEGIN
            logging_pkg.log_err(
                p_msg => :msg
                p_err => :err
            );
        END;`,
        {
            msg: {
                val: msg,
                dir: oracledb.BIND_IN
            },
            err: {
                val: err.message,
                dir: oracledb.BIND_IN
            }
        }
    );
}
/

次のようにモジュールjs_err_handle_modを使用して、ファンクションjs_err_handle_mod_fを作成します:

CREATE OR REPLACE FUNCTION js_err_handle_mod_f
RETURN BOOLEAN
AS MLE MODULE js_err_handle_mod
SIGNATURE 'errorHandlingDemo()';
/

これで、ファンクションをコールし、戻り値を使用して、処理が成功したかどうかを確認できます:

DECLARE
    l_success boolean := false;
BEGIN
    l_success := js_err_handle_mod_f;

    IF l_success THEN
        DBMS_OUTPUT.PUT_LINE('normal, successful completion');
    ELSE
        DBMS_OUTPUT.PUT_LINE('an error has occurred');
    END IF;
END;
/

この場合、エラーはMLEモジュール内で処理されます。エラーはアプリケーションによって記録されるため、管理者は状況を評価して修正アクションを実行できます。

例6-15 JavaScriptのthrow()コマンドを使用したエラー処理

この例は、catchブロックにおけるJavaScriptのthrow()コマンドの使用を示しています。例6-14js_err_handle_modについて表示された画面出力とは異なり、コール側のPL/SQLブロックがエラーを捕捉し、それを適宜処理するか、再度生成する必要があります。

CREATE OR REPLACE MLE MODULE js_throw_mod
LANGUAGE JAVASCRIPT AS

/**
 *a similar example as Example 6-14, however, rather than
 *processing the error in the JavaScript code, it is re-thrown up the call stack.
 *It is now up to the called to handle the exception. The try/catch block is not 
 *strictly necessary but is used in this example as a cleanup step to remove Global
 *Temporary Tables (GTTs) and other temporary objects that are no longer required.
*/
export function rethrowError(){
    
    try{
        const result = session.execute(
            `INSERT INTO 
                surelyThisTableDoesNotExist
            VALUES
                (1)`
        );

        console.log(`there were ${result.rowsAffected} rows inserted`);

    } catch(err){
        cleanUpBatch();

        throw(err);
    }

    //further processing
}

function cleanUpBatch(){
    console.log(
        `this is where you would perform cleanup tasks before returning`
    );
}
/

次のコール仕様を使用すると、エラーの捕捉に失敗すると予期しないエラーが発生し、エンド・ユーザーまでコール・スタックを伝播できます。

CREATE OR REPLACE PROCEDURE rethrow_err_proc
AS MLE MODULE js_throw_mod
SIGNATURE 'rethrowError()';
/

BEGIN
    rethrow_err_proc;
END;
/

結果:

BEGIN
*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-04171: at rethrowError (USER1.JS_THROW_MOD:11:24)
ORA-06512: at "USER1.RETHROW_ERROR_PROC", line 1
ORA-06512: at line 2

エンド・ユーザーに、このタイプのエラーが表示されないようにします。かわりに、よりわかりやすいメッセージを表示します。この例を続けて、単純な修正として例外ブロックを追加します:

BEGIN
    rethrow_err_proc;
EXCEPTION
    WHEN OTHERS THEN
        logging_pkg.log_err(
            'something went wrong',
            sqlerrm
        );
        --this would be shown on the user interface;
        --for the sake of demonstration this workaround
        --is used to show the concept
        DBMS_OUTPUT.PUT_LINE(
            'ERROR: the process encountered an unexpected error'
        );
        DBMS_OUTPUT.PUT_LINE(
            'please inform the administrator referring to application error 1234'
        );
END;
/

結果:

ERROR: the process encountered an unexpected error
please inform the administrator referring to application error 1234

PL/SQL procedure successfully completed.

JSONデータの操作

リレーショナル構造の一部としてのJSONデータの使用、より具体的には(リレーショナル)表でのJSON列の使用について説明します。

Oracle Databaseは、トランザクション、索引付け、宣言的問合せおよびビューを含むリレーショナル・データベースの機能を使用してJSONをネイティブにサポートしています。リレーショナル・データとは異なり、JSONデータは、スキーマがなくても、データベースへの格納、索引付けおよび問合せが可能です。脚注1

Oracleでは、データベース内に格納されたJSONデータへのアクセス用に、Simple Oracle Document Access (SODA) APIファミリも提供しています。SODAは、リレーショナル・データベース機能やSQLおよびPL/SQLなどの言語に関する知識を使用しないスキーマレス・アプリケーション開発向けに設計されています。これにより、ドキュメントがデータベースにどのように格納されているかを理解していなくても、ドキュメントのコレクションを作成してOracle Databaseに格納し、それらを取得したり、問い合せることができます。

JSONデータは、アプリケーション層とデータベースの間で情報を交換するために広く使用されています。Oracle REST Data Services (ORDS)は、データベースへのRESTコールを行うための最も便利なツールです。例6-16に、この概念を示します。

JSONの操作は、JavaScriptのコア機能の1つです。受信JSONドキュメントは、JSON.parse()を使用して解析する必要はなく、すぐに使用できます。マイクロサービス・アーキテクチャでは、データベース内のJavaScriptによって提供される拡張オプションが非常に有益です。

関連項目:

例6-16 データベース表へのJSONデータの挿入

この例は、REST APIがORDSで公開されており、ユーザーがJSONデータをデータベースにPOSTできることを前提としています。これにより、管理者は、departments表にさらに部門をアップロードできます。JSONデータを受け取ると、MLEモジュールはJSON_TABLE()を使用して、JSONデータ構造をリレーショナル・モデルに変換します。

CREATE OR REPLACE MLE MODULE RestApiModule LANGUAGE JAVASCRIPT AS

import oracledb from "mle-js-oracledb";

export function insertDepartments(depts) {

    // the input parameter must be valid JSON. This is ensured by using
    // JSON as a parameter in the call specification
    
    const conn = oracledb.defaultConnection();
    
    // convert JSON input to relational data and insert into a table
    const result = conn.execute(`
        INSERT INTO departments (
            department_id,
            department_name,
            manager_id,
            location_id
        ) 
        SELECT 
            jt.* 
        FROM json_table(:depts, '$[*]' columns
            department_id    path '$.department_id',
            department_name  path '$.department_name',
            manager_id       path '$.manager_id',
            location_id      path '$.location_id'
        ) jt`,
        {
            depts: {
                val: depts,
                type: oracledb.DB_TYPE_JSON
            }
        }
    );
}
/

次の匿名PL/SQLブロックを使用してRESTコールをシミュレートすると、追加の部門を表に挿入できます:

DECLARE
    l_success boolean := false;
    l_depts JSON;
BEGIN
    l_depts := JSON('[
        {
            "department_id": 1010,
            "department_name": "New Department 1010",
            "manager_id": 200,
            "location_id": 1700
        },
        {
            "department_id": 1020,
            "department_name": "New Department 1020",
            "manager_id": 201,
            "location_id": 1800
        },
        {
            "department_id": 1030,
            "department_name": "New Department 1030",
            "manager_id": 114,
            "location_id": 1700
        },
        {
            "department_id": 1040,
            "department_name": "New Department 1040",
            "manager_id": 203
            "location_id": 2400
        }]'
    );

    l_success := restAPIExample(l_depts);

    IF NOT l_success THEN
        RAISE_APPLICATION_ERROR(
            -20001,
            'an unexpected error occurred'
        );
    END IF;
END;
/

次の問合せで示されているように、データが正常に挿入されました:

SELECT *
FROM hr.departments
WHERE department_id > 1000;

結果:

DEPARTMENT_ID DEPARTMENT_NAME                MANAGER_ID LOCATION_ID
------------- ------------------------------ ---------- -----------
         1010 New Department 1010                   200        1700
         1020 New Department 1020                   201        1800
         1030 New Department 1030                   114        1700
         1040 New Department 1040                   203        2400

例6-17 JavaScriptを使用したJSONデータの操作

JSON_TABLEやJSON_TRANSFORMなどのSQLファンクションを使用するのではなく、JavaScriptでもJSONデータ操作を実行できます。

この例は、『Oracle Database JSON開発者ガイド』で定義されているJ_PURCHASEORDER表に基づいています。この表には、複数の顧客の購買オーダーを含むJSONドキュメントが格納されます。各購買オーダーは、1つ以上の明細品目で構成されます。

次のファンクションaddFreeItem()を使用すると、しきい値を超える商品を注文する顧客に無料アイテムを追加できます。

CREATE OR REPLACE MLE MODULE purchase_order_mod
LANGUAGE JAVASCRIPT AS

/**
 *a simple function accepting a purchase order and checking whether
 *its value is high enough to merit the addition of a free item
 *
 *@param {object} po the purchase order to be checked
 *@param {object} freeItem which free item to add to the order free of charge
 *@param {number} threshold the minimum order value before a free item can be added
 *@param {boolean} itemAdded a flag indicating whether the free item was successfully added
 *@returns {object} the potentially updated purchaseOrder
 *@throws exception in case
 *    -any of the mandatory parameters is null
 *    -in the absence of line items
 *    -if the free item has already been added to the order
 */
export function addFreeItem(po, freeItem, threshold, itemAdded){
    
    //ensure values for parameters have been provided
    if(po == null || freeItem == null || threshold == null){
        throw new Error(`mandatory parameter either not provided or null`);
    }

    //make sure there are line items provided by the purchase order
    if(po.LineItems === undefined) {
        throw new Error(
            `PO number ${po.PONumber} does not contain any line items`
        );
    }

    //bail out if the free item has already been added to the purchase order
    if(po.LineItems.find(({Part}) => Part.Description === freeItem.Part.Description)){
        throw new Error(`${freeItem.Part.Description} has already been added to order ${po.PONumber}`);
    }

    //In, Out, and InOut Parameters are implemented in JavaScript using 
    //special interfaces
    itemAdded.value = false;

    //get the total order value
    const poValue = po.LineItems
        .map(x => x.Part.UnitPrice * c.Quantity)
        .reduce(
            (accumulator, currentValue) => accumulator + currentValue, 0
        );

    //add a free item to the purchase order if its value exceeds 
    //the threshold
    if(poValue > threshold){
        
        //update the ItemNumber
        freeItem.ItemNumber = (po.LineItems.length + 1)
        po.LineItems.push(freeItem);
        itemAdded.value = true;
    }

    return po;
}
/

JavaScriptでのバインド・パラメータの使用の詳細は、「OUTおよびIN OUTパラメータ」を参照してください。

MLEでのラージ・オブジェクト(LOB)の使用

MLE JavaScriptドライバでCLOBおよびBLOBを処理するには、PL/SQLラッパー型を使用します。

MLE JavaScriptドライバでのCLOB (キャラクタ・ラージ・オブジェクト)やBLOB (バイナリ・ラージ・オブジェクト)などのラージ・オブジェクトの処理は、node-oracledbドライバとは異なります。Node.jsストリーム・インタフェースを使用するかわりに、PL/SQLラッパー型を使用します。BLOBおよびCLOBのラッパー型は、それぞれOracleBlobおよびOracleClobと呼ばれます。これらは、mle-js-plsqltypesで定義されます。ほとんどのタイプはグローバル・スコープで公開され、モジュールをインポートせずに参照できます。

ノート:

一般的にLOBに属するとされるBFILEはサポートされていません。

関連項目:

mle-js-plsqltypesおよびその他のJavaScript組込みモジュールの詳細は、サーバー側JavaScript APIのドキュメントを参照してください

トピック

LOBの書込み

例は、最終的に表に挿入されるCLOBを初期化し、それに書き込む方法を示しています。

例6-18 表へのCLOBの挿入

この例では、CLOBを表に挿入する方法を示します。この表には、主キーとして使用されるID列とCという名前のCLOB列の2つの列が定義されます。

CREATE TABLE mle_lob_example ( 
    id NUMBER GENERATED ALWAYS AS IDENTITY, 
    CONSTRAINT pk_mle_blob_table PRIMARY KEY(id), 
    c  CLOB 
); 

CREATE OR REPLACE PROCEDURE insert_clob
AS MLE LANGUAGE JAVASCRIPT
q'~
//OracleClob is exposed in the global scope and does not require
//importing 'mle-js-plsqltypes', similar to how oracledb is available
let theClob = OracleClob.createTemporary(false);

theClob.open(OracleClob.LOB_READWRITE);
theClob.write(
    1,
    'This is a CLOB and it has been inserted by the MLE JavaScript SQL Driver'
);

const result = session.execute(
    `INSERT INTO mle_lob_example(c) VALUES(:theCLOB)`,
    {
        theCLOB:{
            type: oracledb.ORACLE_CLOB,
            dir: oracledb.BIND_IN,
            val: theCLOB
        }
    }
);

//it is best practice to close the handle to free memory
theCLOB.close();
~';
/

CLOBとBLOBは、mle-js-plsqltypesで定義されています。最も一般的に使用される型は、グローバル・スコープで提供され、mle-js-plsqltypesのインポートのレンダリングが不要になります。

最初のステップでは、キャッシュされていない一時LOBロケータを作成します。LOBが正常に初期化されると、読取りおよび書込み操作のためにオープンされます。文字列は、オフセット1でCLOBに書き込まれます。この時点まで、LOBはメモリー内に存在します。session.execute()をコールすると、CLOBが表に挿入されます。close()メソッドをコールすると、CLOBがクローズされ、関連付けられたメモリーが解放されます。

LOBの読取り

例を使用して、CLOBを選択した後、fetchInfoプロパティを使用してCLOBの内容を文字列として読み取る方法を示します。

データベースからのLOBの読取りは、他の列の読取りと変わりません。例6-19に、例6-18で定義されているプロシージャinsert_clobによって挿入された行をフェッチする方法を示します。

例6-19 LOBの読取り

CREATE OR REPLACE FUNCTION read_clob(
    "p_id" NUMBER
) RETURN VARCHAR2
AS MLE LANGUAGE JAVASCRIPT
q'~
const result = session.execute(
    `SELECT c
     FROM mle_lob_example
     WHERE id = :id`,
    {
        id:{
            type: oracledb.NUMBER,
            dir: oracledb.BIND_IN,
            val: p_id
        }
    },
    {
        fetchInfo:{
            "C": {type: oracledb.STRING}
        },
        outFormat: oracledb.OBJECT
    }
);
if (result.rows.length === 0){
    throw new Error(`No data found for ID ${id}`);
} else {
    for (let row of result.rows){
        return row.C;
    }
}
~';
/

ファンクションread_clobは、パラメータとしてIDを受け取ります。これは、CLOBを含む行を識別するために、select文のWHERE句でバインド変数として使用されます。session.execute()を使用して渡されるfetchInfoプロパティは、CLOBを文字列としてフェッチするようデータベースに指示します。

node-oracledbとmle-js-oracledbの間のAPIの相違点

接続管理や型マッピングを処理する方法など、node-oracledbmle-js-oracledbの間にはいくつかの相違点があります。

関連項目:

JavaScript組込みモジュールの詳細は、サーバー側JavaScript APIのドキュメントを参照してください

トピック

同期APIとエラー処理

node-oracledbと比較して、mle-js-oracledbドライバは同期モードで動作し、例外が発生すると例外をスローします。非同期動作が必要な場合は、mle-js-oracledbへのコールを非同期ファンクションにラップします。

同期操作中は、結果またはエラーが戻されるまでAPIコールがブロックされます。SQL実行によって発生したエラーはJavaScript例外として報告され、それ以外の場合は、node-oracledbErrorオブジェクトと同じプロパティが戻されます。

次のメソッドは、promiseを戻さず、コールバック・パラメータを使用しません。結果を戻すか、例外をスローします。
  • connection.execute
  • connection.executeMany
  • connection.getStatementInfo
  • connection.getSodaDatabase
  • connection.commit
  • connection.rollback
  • resultset.close
  • resultset.getRow
  • resultset.getRows
次のメソッドは同期方式では実装できず、MLE JavaScriptドライバでは除外されます。
  • connection.break
node-oracledbには、LOB型へのストリーミング・アクセスを提供するLOB (ラージ・オブジェクト)クラスが用意されています。このLOBクラスは非同期Node.jsストリームAPIを実装し、同期MLE JavaScript環境ではサポートできません。ラージ・オブジェクトは、MLE JavaScriptドライバの代替APIを使用してサポートされます。これらの理由により、LOB関連の次の機能はサポートされていません。
  • connection.createLob
  • property oracledb.lobPrefetchSize
  • constant oracledb.BLOB
  • constant oracledb.CLOB
node-oracledbは、Node.jsストリームAPIに基づく別の機能である、問合せ結果の非同期ストリーミングも実装しています。MLE JavaScriptドライバで使用される同期インタフェースではストリーミングAPIを表現できないため、次の機能は使用できません。
  • connection.queryStream()
  • resultSet.toQueryStream()

接続処理

MLE JavaScriptドライバでの接続処理の方法について説明します。

サーバー側のMLE JavaScriptドライバを介して実行されるSQL文はすべて、JavaScriptプログラムを実行している現行のセッションで実行されます。SQL文は、JavaScriptコードの実行を代理される元のユーザーの権限で実行されます。node-oracledb APIと同様に、MLE JavaScriptドライバを使用するJavaScriptコードは、SQL文を実行するために接続オブジェクトを取得する必要があります。ただし、使用可能な接続は、現行のデータベース・セッションへの暗黙的な接続のみです。

JavaScriptコードは、MLE固有のoracledb.defaultConnection()メソッドを使用して、現行のセッションへの接続を取得する必要があります。起動のたびに、セッション接続を表す接続オブジェクトが戻されます。node-oracledboracledb.createConnectionメソッドを使用した接続の作成は、MLE JavaScriptドライバではサポートされていません。また、接続プールの作成もサポートされていません。接続オブジェクトは暗黙的にクローズされるため、MLE JavaScriptドライバでは、connection.close()へのコールを使用できません。

また、MLE JavaScriptドライバには文カーソル・キャッシュがないため、stmtCacheSizeプロパティはありません。

Real Application Cluster (RAC)オプションでは、アプリケーションの可用性を高めるように設計された追加機能が提供されます。これには高速アプリケーション通知(FAN)およびランタイム・ロード・バランシング(RLB)が含まれますが、これらはいずれもMLE JavaScriptドライバではサポートされていません。

トランザクション管理

トランザクション管理に関しては、サーバー側のMLE JavaScriptコードはPL/SQLプロシージャおよびファンクションとまったく同様に動作します。

JavaScriptプログラムは、コール元のSQLまたはPL/SQL文の現行のトランザクション・コンテキストで実行されます。進行中のトランザクションを制御するには、COMMITSAVEPOINTまたはROLLBACKコマンドを実行します。または、メソッドconnection.commit()およびconnection.rollback()を使用することもできます。

MLE JavaScript SQLドライバの接続を明示的にクローズすることはできません。node-oracledbの、接続をクローズすると、トランザクションのロールバックが行われる動作に依存するアプリケーションでは、調整が必要になります。MLE JavaScript SQLドライバでは、トランザクションの暗黙的なコミットもロールバックも行われません。

node-oracledbドライバにはauto-commitフラグが用意されており、デフォルトではfalseに設定されています。MLE JavaScript SQLドライバには、この機能は実装されていません。指定した場合、connection.execute()ファンクションはこのパラメータを無視します。

型マッピング

MLE JavaScriptドライバは、PL/SQL型とJavaScript型の間の変換に関して、node-oracledbの動作に準拠しています。

デフォルトでは、PL/SQL型はネイティブのJavaScript型にマップされます(BLOBとCLOBを除きます)。問合せ結果からフェッチされた値は暗黙的に変換されます。MLEの型マッピングの詳細は、「MLEの型変換」を参照してください。

node-oracledbと同様に、文字以外のデータ型からの変換およびその逆の変換は、NLSセッション・パラメータの影響を直接受けます。MLEのランタイム・ロケールは、これらの変換には影響しません。

ネイティブのJavaScript型とPL/SQLデータ型の間の変換時に精度が損なわれないように、MLE JavaScriptドライバでは新しいラッパー型が導入されています。
  • oracledb.ORACLE_NUMBER
  • oracledb.ORACLE_CLOB
  • oracledb.ORACLE_BLOB
  • oracledb.ORACLE_TIMESTAMP
  • oracledb.ORACLE_TIMESTAMP_TZ
  • oracledb.ORACLE_DATE
  • oracledb.ORACLE_INTERVAL_YM
  • oracledb.ORACLE_INTERVAL_DS

node-oracledbと同様に、connection.execute()fetchInfoプロパティを使用して、JavaScript型へのデフォルト・マッピングを状況に応じてオーバーライドできます。暗黙的な変換や精度の損失を回避するために、oracledb.ORACLE_NUMBERのような型定数を使用して、特定のNUMBER列の型マッピングをオーバーライドできます。

また、JavaScript MLE SQLドライバには、PL/SQL型のデフォルト・マッピングをグローバルに変更する方法も用意されています。対応する型定数がoracledb.fetchAsPlsqlWrapperプロパティに含まれている場合、Oracle値は前述のSQLラッパー型としてフェッチされます。既存のプロパティoracledb.fetchAsStringと同様に、この動作は、fetchInfoおよびoracledb.DEFAULTを使用してオーバーライドできます。MLE JavaScriptはBufferクラスをサポートしておらず、かわりにUint8Arrayを使用するため、node-oracledbのプロパティoracledb.fetchAsBuffermle-js-oracledbには存在せず、かわりにoracledb.fetchAsUint8Arrayを使用します。

デフォルトでJavaScript SQLラッパー型をフェッチするように型マッピングを変更することは、次のシナリオに有用です:
  • Oracle値が主に問合せとDML文の間で移動されるため、PL/SQL型とJavaScript型の間の型変換が不要なオーバーヘッドとなっている
  • データ損失を避けることが非常に重要である

例6-20 JavaScriptのネイティブ・データ型の使用とラッパー型の使用

この例では、計算にJavaScriptのネイティブ・データ型を使用した場合の効果を示します。また、JavaScriptのネイティブ型を使用した場合とラッパー型を使用した場合の精度の損失を比較します。

CREATE OR REPLACE MLE MODULE js_v_wrapper_mod
LANGUAGE JAVASCRIPT AS

/**
 *There is a potential loss of precision when using native
 *JavaScript types to perform certain calculations. This
 *is caused by the underlying implementation as a floating
 *point number
*/

export function precisionLoss(){
    
    let summand1 = session
        .execute(`SELECT 0.1 summand1`)
        .rows[0].SUMMAND1;

    let summand2 = session
        .execute(`SELECT 0.2 summand2`)
        .rows[0].SUMMAND2;

    const result = summand1 + summand2;

    console.log(`precisionLoss() result: ${result}`);
}

/**
 *Use an Oracle data type to preserve precision. The above
 *example can be rewritten using the OracleNumber type as
 *follows
*/
export function preservePrecision(){
    
    //instruct the JavaScript SQL driver to return results as
    //Oracle Number. This could have been done for individual
    //statements using the fetchInfo property - the global
    //change applies to this and all future calls
    oracledb.fetchAsPlsqlWrapper = [oracledb.NUMBER];
    let summand1 = session
        .execute(`SELECT 0.1 S1`)
        .rows[0].S1;
    
    let summand2 = session
        .execute(`SELECT 0.2 S2`)
        .rows[0].S2;

    //note that you must use functions defined for type
    //OracleNumber rather than arithmetic operators. For example,
    //summand1.add(summand2) cannot be replaced by summand1+summand2  
    const result = summand1.add(summand2);

    console.log(`preservePrecision() result: ${result}`);
}
/

前述のファンクションを実行すると、精度の違いが即座に明らかになります。

precisionLoss() result: 0.30000000000000004
preservePrecsion() result: .3

グローバルなoracledb.fetchAsPlsqlWrapperプロパティを設定するかわりに、connection.execute()の起動ごとにこの設定をオーバーライドできます。例6-21に、精度をインラインで設定することでprecisionPreservedGlobal()をリライトする方法を示します。

OracleNumber型で使用できるファンクションの詳細は、サーバー側のJavaScript APIドキュメントを参照してください。

例6-21 グローバルなoracledb.fetchAsPlsqlWrapperプロパティのオーバーライド

この例は、精度をインラインで保持することでprecisionPreservedGlobal()をリライトする方法を示すことにより、例6-20を拡張しています。これは、グローバルなoracledb.fetchAsPlsqlWrapperプロパティを設定するかわりに、connection.execute()の起動ごとにこの設定をオーバーライドできることを示しています。

CREATE OR REPLACE PROCEDURE fetch_info_example
AS MLE LANGUAGE JAVASCRIPT
q'~
    let summand1 = session
        .execute(
            `SELECT 0.1 S1`,
            [],
            {
                fetchInfo:{
                    S1:{type: oracledb.ORACLE_NUMBER}
                }
            }
        )
        .rows[0].S1;
    
    let summand2 = session
        .execute(
            `SELECT 0.2 S2`,
            [],
            {
                fetchInfo:{
                    S2:{type: oracledb.ORACLE_NUMBER}
                }
            }
        )
        .rows[0].S2;

    const result = summand1.add(summand2);

    console.log(`
    preservePrecision():
    summand1: ${summand1}
    summand2: ${summand2}
    result: ${result}
    `);
~';
/

サポートされていないデータ型

MLE JavaScriptドライバでは現在、次のデータ型はサポートされていません:
  • LONG
  • LONG RAW
  • XMLType
  • BFILE
  • REF CURSOR

MLE JavaScript SQLドライバで使用できないその他の機能

MLE JavaScriptドライバで使用できる機能とnode-oracledbで使用できる機能の間の違いについて説明します。

MLE JavaScriptドライバにおけるエラー処理は、node-oracledbのようにコールバック/promiseを使用するのではなく、JavaScript例外フレームワークに依存します。MLE JavaScript SQLドライバによってスローされるエラーは、node-oracledbで使用可能なErrorオブジェクトと同じです。

node-oracledbで使用可能なその他のいくつかのクライアント側機能が、サーバー側のMLE環境ではサポートされていません。MLE JavaScriptドライバでは、これらの機能のAPIは除外されます。

現在、次の機能は使用できません:
  • 連続問合せ通知(CQN)
  • アドバンスト・キューイングはネイティブにサポートされず、PL/SQL APIを回避策として使用できます
  • Connection.subscribe()
  • Connection.unsubscribe()
  • oracledbクラス内のすべての連続問合せ通知定数
  • oracledbクラス内のすべてのサブスクリプション定数


脚注凡例

脚注1: JSONスキーマを、データベース・スキーマの概念と混同しないでください。Oracle Databaseのデータベース・スキーマは、データベース・ユーザーがネーミングの競合のリスクなしに、表、索引、ビューなどのオブジェクトを作成するための個別のネームスペースです。