6 MLE JavaScript SQLドライバからのPL/SQLおよびSQLのコール
- MLE JavaScript SQLドライバの概要
MLE JavaScriptドライバは、Node.js用のクライアント側Oracle SQLドライバであるnode-oracledb
によく似たモデルとなっています。 - MLE JavaScriptドライバを使用したデータの選択
直接フェッチまたはResultSet
オブジェクトを使用してデータを選択できます。 - データ変更
MLE JavaScript SQLドライバを使用してデータを変更します。 - バインド変数
バインド変数を使用して、データベースに渡されるデータまたはデータベースから取得されるデータを制御します。 - MLE JavaScript SQLドライバからのPL/SQLの起動
MLE JavaScriptドライバを使用して、PL/SQLからファンクションおよびプロシージャをコールします。 - SQL文のエラー処理
JavaScriptには、Javaのような例外フレームワークが用意されています。node-oracledb
のようにError
オブジェクトをpromiseまたはコールバックとして戻すのではなく、MLE JavaScriptドライバはエラーのスローを使用します。この概念は、PL/SQL開発者にとって非常になじみ深いものです。 - JSONデータの操作
リレーショナル構造の一部としてのJSONデータの使用、より具体的には(リレーショナル)表でのJSON列の使用について説明します。 - MLEでのラージ・オブジェクト(LOB)の使用
MLE JavaScriptドライバでCLOBおよびBLOBを処理するには、PL/SQLラッパー型を使用します。 - node-oracledbとmle-js-oracledbの間のAPIの相違点
接続管理や型マッピングを処理する方法など、node-oracledb
とmle-js-oracledb
の間にはいくつかの相違点があります。
MLE JavaScript SQLドライバの概要
node-oracledb
によく似たモデルとなっています。
サーバー側ドライバとクライアント側ドライバの間のこの密接な関係により、クライアント側のJavaScriptコードをNode.jsまたはDenoからデータベースに移植するために必要な労力が軽減されます。サーバー側の環境に適切にマップできない機能は、MLEおよびMLE JavaScriptドライバでは除外され、エラーをスローします。
これは、コードのどの部分に変更が必要かを識別するために役立ちます。さらに、MLE JavaScript実装は純粋なJavaScript実装です。ウィンドウ・オブジェクトおよびファイルとネットワークの直接I/Oなど、ECMAScript標準に含まれない特定の機能はMLEでは使用できません。
mle-js-oracledb
SQLドライバは同期操作モデルにデフォルト設定され、async/awaitによる非同期実行を部分的にサポートしています。
ノート:
本番コードはエラー処理およびロギングに関する業界のベスト・プラクティスに準拠している必要がありますが、この章の例ではわかりやすくするために省略されています。また、読みやすいことから、ほとんどの例で同期実行モデルを採用しています。関連項目:
-
組込みJavaScriptモジュールの詳細は、サーバー側JavaScript APIのドキュメントを参照してください
トピック
- MLE JavaScriptドライバの操作
MLE JavaScriptドライバを操作するための汎用的なワークフロー。 - MLE JavaScriptドライバの接続管理
MLE JavaScriptドライバの接続管理を処理する際の考慮事項。 - SQL文の実行の概要
Connection
クラスのexecute()
メソッドによって、単一のSQLまたはPL/SQL文を実行できます。問合せ結果は、単一のJavaScript配列で戻すことも、ResultSet
オブジェクトを使用してバッチでフェッチすることもできます。 - node-oracledbとmle-js-oracledbの処理の比較
node-oracledb
ドキュメントでは、async/awaitインタフェースの使用が推奨されています。クライアントとサーバーの対話の性質により、nodeとデータベースの間に含まれる処理の大部分が非同期的に実行されます。
MLE JavaScriptドライバの操作
MLE JavaScriptドライバを操作するための汎用的なワークフロー。
高レベルでは、MLE JavaScriptドライバの操作は、クライアント側のnode-oracledb
ドライバの使用によく似ています。つまり、次のようになります。
- 既存のデータベース・セッションへの接続ハンドルを取得します。
- その接続を使用してSQL文を実行します。
- 実行された文によって戻された結果オブジェクトと、発生した可能性のあるデータベース・エラーを確認します。
- select文の場合は、結果のカーソルを反復処理します。
- データを操作する文については、トランザクションをコミットするかロールバックするかを決定します。
クライアント側のNode.jsまたはDenoから移植されていないアプリケーションでは、グローバル・スコープで使用可能な頻繁に使用される多数の変数など、MLE JavaScript SQLドライバで使用可能なコーディング支援のメリットが得られます。これらの変数には、次のものが含まれます。
OracleDb
ドライバ・オブジェクトのoracledb
- デフォルトの接続オブジェクトの
session
SodaDatabase
オブジェクトのsoda
さらに、次の型も使用できます。
OracleNumber
OracleClob
OracleBlob
OracleTimestamp
OracleTimestampTZ
OracleDate
これらのオブジェクトをグローバル・スコープで使用できるため、ボイラープレート・コードを記述する必要性が少なくなります。MLE JavaScript SQLドライバで使用可能なグローバル・シンボルの詳細は、サーバー側のJavaScript APIドキュメントを参照してください。
親トピック: MLE JavaScript SQLドライバの概要
MLE JavaScriptドライバの接続管理
MLE JavaScriptドライバの接続管理は、クライアント・ドライバと比較して大幅に簡素化されています。JavaScriptストアド・プロシージャが起動されると、データベース・セッションがすでに存在するため、接続、接続プールおよびセキュアな資格証明管理の確立や破棄その他多くのことについて心配する必要はありません。
mle-js-oracledb
モジュールのgetDefaultConnection()
メソッドに注意するか、グローバル・セッション・オブジェクトを使用することで済みます。
親トピック: MLE JavaScript SQLドライバの概要
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
親トピック: MLE JavaScript SQLドライバの概要
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 SQLドライバの概要
MLE JavaScriptドライバを使用したデータの選択
ResultSet
オブジェクトを使用してデータを選択できます。
出力形式としては配列とオブジェクトのどちらかを選択できます。デフォルトでは、JavaScriptオブジェクトを使用してデータが直接フェッチで戻されます。
トピック
- 直接フェッチ: 配列
直接フェッチは、MLE JavaScriptドライバのデフォルトです。 - 直接フェッチ: オブジェクト
直接フェッチを使用すると、デフォルトでJavaScriptオブジェクトが戻されます。 - ResultSetとしての行のフェッチ: 配列
直接フェッチを使用するかわりに、ResultSet
オブジェクトを使用できます。 - ResultSetとしての行のフェッチ: ResultSetオブジェクトの反復処理
ResultSet.getRow()
およびResultSet.getRows()
ファンクションに加えて、MLE JavaScriptドライバのResultSet
は、ResultSet
を反復処理するプロセスを簡略化するiterableプロトコルとiteratorプロトコルを実装します。
直接フェッチ: 配列
直接フェッチでは、問合せ結果がresult.rows
で提供されます。outFormat
をoracledb.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のドキュメントを参照してください。
直接フェッチ: オブジェクト
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
としてフェッチするには、outFormat
をoracledb.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ドライバを使用してデータの挿入、更新、削除およびマージを行うこともできます。これらの操作には、データを選択するときに使用するのと同じ一般的なワークフローを適用できます。
例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文には、接頭辞としてコロンが付いた識別子で示されるバインド変数を含めることができます。これらのパラメータは、別個に指定された値が実行時に代入される文内の場所、または実行後に値が戻される場所を示します。
IN
バインド変数OUT
バインド変数IN OUT
バインド変数
IN
バインドは、データベースに渡される値です。OUT
バインドは、データベースからデータを取得するために使用されます。IN OUT
バインドは渡され、文の実行後に異なる値を戻すことができます。
文字列連結やテンプレート・リテラルを使用してSQLまたはPL/SQL文を構成するよりも、バインド変数を使用することをお薦めします。バインド変数を使用すると、パフォーマンスとセキュリティの両面で有益である可能性があります。バインド変数を使用すると、Oracle Databaseでは、リソースと時間を消費するハード解析操作を実行する必要がありません。かわりに、カーソル・キャッシュにすでに存在するカーソルを再利用できます。
ノート:
バインド変数は、CREATE TABLE
などのDDL文で使用することも、問合せのテキストを代入することもできません。データのみを代入できます。
トピック
- 名前によるバインドと位置によるバインドの使用
バインド変数は、名前による方法と位置による方法の2つの方法で使用されます。これらのオプションは相互に排他的であるため、指定されたSQLコマンドについて一方を選択する必要があります。 - RETURNING INTO句
RETURNING INTO
句の使用について説明します。 - バッチ操作
connection.execute()
ファンクションをコールすることに加えて、connection.executeMany()
を使用してバッチ操作を実行することもできます。
名前によるバインドと位置によるバインドの使用
バインド変数は、名前による方法と位置による方法の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}`
);
}
~';
/
IN
とOUT
の両方のバインド変数を扱っています:
firstEmpID
およびlastEmpID
では、更新するデータ範囲を指定しますoldLastName
は、更新前の姓をすべて含む配列ですnewLastName
は、新しい値を含む別の配列です
親トピック: バインド変数
バッチ操作
connection.execute()
ファンクションをコールすることに加えて、connection.executeMany()
を使用してバッチ操作を実行することもできます。
connection.executeMany()
を使用することはconnection.execute()
を複数回コールすることと似ていますが、必要な作業が少なくなります。これは、複数の行を挿入または更新するときなど、バッチ変更を処理するための効率的な方法です。connection.executeMany()
メソッドを問合せに使用することはできません。
connection.execute()
には、SQL文で処理する変数を含む配列が必要です。例6-12のbindData
配列には、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
プロパティで提供します。oldLastName
とnewLastName
はどちらも配列です。配列の長さは、更新された行の数を表します。
親トピック: バインド変数
MLE JavaScript SQLドライバからの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文のエラー処理
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-14でjs_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によって提供される拡張オプションが非常に有益です。
関連項目:
- SODAおよびデータベース内のJavaScriptの詳細は、「MLE JavaScriptコードでのSODAコレクションの操作」を参照してください
- Oracle DatabaseでのJSONの使用の詳細は、『Oracle Database JSON開発者ガイド』を参照してください
例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 (バイナリ・ラージ・オブジェクト)などのラージ・オブジェクトの処理は、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がクローズされ、関連付けられたメモリーが解放されます。
親トピック: MLEでのラージ・オブジェクト(LOB)の使用
LOBの読取り
例を使用して、CLOBを選択した後、fetchInfo
プロパティを使用して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を文字列としてフェッチするようデータベースに指示します。
親トピック: MLEでのラージ・オブジェクト(LOB)の使用
node-oracledbとmle-js-oracledbの間のAPIの相違点
接続管理や型マッピングを処理する方法など、node-oracledb
とmle-js-oracledb
の間にはいくつかの相違点があります。
関連項目:
JavaScript組込みモジュールの詳細は、サーバー側JavaScript APIのドキュメントを参照してください
トピック
- 同期APIおよびエラー処理
node-oracledb
と比較して、mle-js-oracledb
ドライバは同期モードで動作し、例外が発生すると例外をスローします。非同期動作が必要な場合は、mle-js-oracledb
へのコールを非同期ファンクションにラップします。 - 接続処理
MLE JavaScriptドライバでの接続処理の方法について説明します。 - トランザクション管理
トランザクション管理に関しては、サーバー側のMLE JavaScriptコードはPL/SQLプロシージャおよびファンクションとまったく同様に動作します。 - 型マッピング
MLE JavaScriptドライバは、PL/SQL型とJavaScript型の間の変換に関して、node-oracledb
の動作に準拠しています。 - サポートされていないデータ型
MLE JavaScriptドライバでは現在、次のデータ型はサポートされていません: - MLE JavaScript SQLドライバで使用できないその他の機能
MLE JavaScriptドライバで使用できる機能とnode-oracledb
で使用できる機能の間の違いについて説明します。
同期APIとエラー処理
node-oracledb
と比較して、mle-js-oracledb
ドライバは同期モードで動作し、例外が発生すると例外をスローします。非同期動作が必要な場合は、mle-js-oracledb
へのコールを非同期ファンクションにラップします。
同期操作中は、結果またはエラーが戻されるまでAPIコールがブロックされます。SQL実行によって発生したエラーはJavaScript例外として報告され、それ以外の場合は、node-oracledb
のError
オブジェクトと同じプロパティが戻されます。
connection.execute
connection.executeMany
connection.getStatementInfo
connection.getSodaDatabase
connection.commit
connection.rollback
resultset.close
resultset.getRow
resultset.getRows
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ドライバを介して実行されるSQL文はすべて、JavaScriptプログラムを実行している現行のセッションで実行されます。SQL文は、JavaScriptコードの実行を代理される元のユーザーの権限で実行されます。node-oracledb
APIと同様に、MLE JavaScriptドライバを使用するJavaScriptコードは、SQL文を実行するために接続オブジェクトを取得する必要があります。ただし、使用可能な接続は、現行のデータベース・セッションへの暗黙的な接続のみです。
JavaScriptコードは、MLE固有のoracledb.defaultConnection()
メソッドを使用して、現行のセッションへの接続を取得する必要があります。起動のたびに、セッション接続を表す接続オブジェクトが戻されます。node-oracledb
のoracledb.createConnection
メソッドを使用した接続の作成は、MLE JavaScriptドライバではサポートされていません。また、接続プールの作成もサポートされていません。接続オブジェクトは暗黙的にクローズされるため、MLE JavaScriptドライバでは、connection.close()
へのコールを使用できません。
また、MLE JavaScriptドライバには文カーソル・キャッシュがないため、stmtCacheSize
プロパティはありません。
Real Application Cluster (RAC)オプションでは、アプリケーションの可用性を高めるように設計された追加機能が提供されます。これには高速アプリケーション通知(FAN)およびランタイム・ロード・バランシング(RLB)が含まれますが、これらはいずれもMLE JavaScriptドライバではサポートされていません。
トランザクション管理
JavaScriptプログラムは、コール元のSQLまたはPL/SQL文の現行のトランザクション・コンテキストで実行されます。進行中のトランザクションを制御するには、COMMIT
、SAVEPOINT
またはROLLBACK
コマンドを実行します。または、メソッドconnection.commit()
およびconnection.rollback()
を使用することもできます。
MLE JavaScript SQLドライバの接続を明示的にクローズすることはできません。node-oracledb
の、接続をクローズすると、トランザクションのロールバックが行われる動作に依存するアプリケーションでは、調整が必要になります。MLE JavaScript SQLドライバでは、トランザクションの暗黙的なコミットもロールバックも行われません。
node-oracledb
ドライバにはauto-commitフラグが用意されており、デフォルトではfalseに設定されています。MLE JavaScript SQLドライバには、この機能は実装されていません。指定した場合、connection.execute()
ファンクションはこのパラメータを無視します。
型マッピング
node-oracledb
の動作に準拠しています。
デフォルトでは、PL/SQL型はネイティブのJavaScript型にマップされます(BLOBとCLOBを除きます)。問合せ結果からフェッチされた値は暗黙的に変換されます。MLEの型マッピングの詳細は、「MLEの型変換」を参照してください。
node-oracledb
と同様に、文字以外のデータ型からの変換およびその逆の変換は、NLSセッション・パラメータの影響を直接受けます。MLEのランタイム・ロケールは、これらの変換には影響しません。
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.fetchAsBuffer
はmle-js-oracledb
には存在せず、かわりにoracledb.fetchAsUint8Array
を使用します。
- 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 SQLドライバで使用できないその他の機能
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のデータベース・スキーマは、データベース・ユーザーがネーミングの競合のリスクなしに、表、索引、ビューなどのオブジェクトを作成するための個別のネームスペースです。