SODA for In-Database JavaScriptの使用

SODA for In-Database JavaScriptにアクセスする方法と、これを使用してコレクションに対する作成、読取り(取得)、更新および削除(CRUD)操作を実行する方法について説明します。

この項では、SODA for MLE JavaScriptについて説明します。この項のコード・スニペットは、読みやすくするために簡略化されている場合があります。JavaScriptファンクションの全体がリストされるように注意されていますが、単独では実行できません。ファンクション定義をJavaScriptモジュールに埋め込み、MLE JavaScript SQLドライバをインポートすると、これらのコード例がOracle Database 23aiの有効なJavaScriptコードに変換されます。

トピック

SODA for In-Database JavaScriptの開始

SODA for In-Database JavaScriptにアクセスする方法、およびこれを使用してデータベース・コレクションを作成し、ドキュメントをコレクションに挿入し、コレクションからドキュメントを取得する方法について説明します。

SODA for MLE JavaScriptの使用を開始する前に、コレクションの格納に使用されるアカウント(この場合はemily)に、直接またはDB_DEVELOPER_ROLEを使用してSODA_APPロールを付与する必要があります:

grant soda_app to emily
SODA機能にアクセスするには、MLE JavaScript SQLドライバを使用する必要があります。コードが起動される時点ではデータベース・セッションが存在するため、追加の接続処理は不要です。例8-1では、次の方法を示します:
  • SODAコレクションの作成、

  • JSONドキュメントの挿入、および

  • コレクション内のすべてのSODAドキュメントに対する反復と、画面へのその内容の出力

例8-1で示す各概念(コレクションの作成、ドキュメントの追加と変更、およびコレクションの削除)については、この章の後半で詳しく説明します。

例8-1 MLE JavaScriptでのSODAの一般的なワークフロー

この例では、MLE JavaScriptでSODAコレクションを使用する一般的なワークフローを示します。この例では、MLEモジュールを使用するかわりに、インライン・コール仕様を実装することでプロセスを簡素化します。

CREATE OR REPLACE PROCEDURE intro_soda(
    "dropCollection" BOOLEAN
) AUTHID CURRENT_USER
AS MLE LANGUAGE JAVASCRIPT
{{
    
  // use the soda object, available in the global scope instead of importing
  // the mle-js-oracledb driver, getting the default connection and extracting
  // the SodaDatabase from it
  const col = soda.createCollection("MyCollection");

  // create a JSON document (based on the HR.EMPLOYEES table for the employee with id 100)
  const doc = {
    "_id" : 100,
    "job_id" : "AD_PRES",
    "last_name" : "King",
    "first_name" : "Steven",
    "email" : "SKING",
    "manager_id" : null,
    "department_id" : 90
  };

  // insert the document into collection
  col.insertOne(doc);

  // find all documents in the collection and print them on screen
  // use a cursor to iterate over all documents in the collection
  const c = col.find()
            .getCursor();

  let resultDoc;
  
  while (resultDoc = c.getNext()){
    const content = resultDoc.getContent();
    console.log(`
      ------------------------------------------
      key:           ${resultDoc.key}
      content (select fields):
      - _id:         ${content._id}
      - job_id:      ${content.job_id}
      - name:        ${content.first_name} ${content.last_name}
      version:       ${resultDoc.version}
      media type:    ${resultDoc.mediaType}`
    );
  }

  // it is very important to close the SODADocumentCursor to free resources
  c.close();

  // optionally drop the collection
  if (dropCollection){
    // there is no auto-commit, the outstanding transaction must be
    // finished before the collection can be dropped
    session.commit();
    col.drop();
  }
}};
/

コードを試すには、お気に入りのIDEを使用してプロシージャを実行します。次に、intro_sodaプロシージャのコール結果の例を示します:

BEGIN
  intro_soda(true);
END;
/

結果:

------------------------------------------
key:           03C202
content (select fields):
- _id:         100
- job_id:      AD_PRES
- name:        Steven King
version:       17EF0F3C102653DDE063DA464664399C
media type:    application/json

PL/SQL procedure successfully completed.

SODA for In-Database JavaScriptによるドキュメント・コレクションの作成

SODA for In-Database JavaScriptを使用して新しいドキュメント・コレクションを作成する方法について説明します。

コレクションを使用すると、ドキュメントを論理的にグループ化できます。コレクションを作成またはコレクションにアクセスする前に、グローバルsodaオブジェクトを使用しないかぎり、いくつかのステップを完了する必要があります。まず接続オブジェクトを作成します。接続オブジェクトは、MLE JavaScriptモジュールでのすべてのSODA対話の開始点です:

// get a connection handle to the database session
const connection = oracledb.defaultConnection();

接続を取得したら、それを使用してConnection.getSodaDatabase()をコールでき、これは、コレクションを作成するための前提条件となります:

// get a SODA database
const db = connection.getSodaDatabase();

SODAデータベースが使用できる状態で最後に行うステップは、コレクションの作成です。コレクション名では大/小文字が区別されます:

// Create a collection with the name "MyCollection".
// This creates a database table, also named "MyCollection",
// to store the collection. If a collection with the same name
// exists, it will be opened
const col = db.createCollection("MyCollection");

前述の文は、デフォルトでJSONドキュメントを格納できるコレクションを作成します。SodaDatabase.createCollection()に渡されているコレクション名が既存のコレクション名である場合、単にそのコレクションがオープンします。また、SodaDatabase.openCollection()を使用して既存する既知のコレクションをオープンすることもできます。

カスタム・メタデータがSodaDatabase.createCollection() (非推奨)に指定されていないかぎり、デフォルトのコレクション・メタデータが提供されます。デフォルトのメタデータには、次の特性があります:

  • コレクション内の各ドキュメントには、次のコンポーネントがあります:
    • キー
    • コンテンツ
    • バージョン
  • コレクションはJSONドキュメントのみを格納できます。
  • ドキュメントのキーとバージョン情報は自動的に生成されます。

オプションのコレクション・メタデータをcreateCollection()のコールに提供できますが、ほとんどの場合、デフォルトのコレクション構成をお薦めします。

すでに同じ名前のコレクションが存在する場合は、単にそのコレクションがオープンし、そのオブジェクトが戻されます。カスタム・メタデータがメソッドに渡され、そのメタデータが既存のコレクションのものと一致しない場合、コレクションはオープンせず、エラーが発生します。一致するには、すべてのメタデータ・フィールドが同じ値である必要があります。

関連項目:

コレクション・メタデータ(カスタム・メタデータを含む)の詳細は、『Oracle Database Simple Oracle Document Access (SODA)の概要』を参照してください。

SODA for In-Database JavaScriptでの既存のドキュメント・コレクションのオープン

メソッドSodaDatabase.openCollection()を使用して、既存のドキュメント・コレクションをオープンしたり、指定された名前が既存のコレクションを示しているかどうかをテストできます。

例8-2 既存のドキュメント・コレクションのオープン

この例では、collectionNameという名前のコレクションをオープンします。SodaDatabase.openCollection()によって戻されるコレクション・オブジェクトがnullでないことを確認することが非常に重要です。リクエストされたコレクションが存在しない場合、メソッドはエラーをスローするのではなく、null値を戻します。

export function openCollection(collectionName) {

    // perform a lookup. If a connection cannot be found by that
    // name no exception nor error are thrown, but the resulting
    // collection object will be null
    const col = soda.openCollection(collectionName);
    if (col === null) {
        throw new Error(`No such collection ${collectionName}`);
    }

    // do something with the collection
} 

SODA for In-Database JavaScriptによる特定のコレクションの有無の確認

SodaDatabase.openCollection()を使用すると、特定のコレクションの存在を確認できます。コレクションの引数が既存のコレクションを示さない場合、nullを返し、それ以外の場合、その名前のコレクションをオープンします。

例8-2では、collectionNameが既存のコレクションの名前でない場合、colには値nullが代入されます。

SODA for In-Database JavaScriptによる既存のコレクションの検出

SodaDatabase.getCollectionNames()を使用すると、特定のSodaDatabaseオブジェクトのすべての既存コレクションの名前をフェッチできます。

コレクションの数が非常に多い場合は、戻される名前の数を制限できます。また、例8-4に示すように、ユーザー定義文字列で始まるコレクションに検索を制限できます。

例8-3 既存のすべてのコレクション名のフェッチ

この例では、メソッドgetCollectionNames()を使用して既存のすべてのコレクションの名前を出力します。

export function printCollectionNames(){
  // loop over all collections in the current user's schema
  const allCollections = soda.getCollectionNames();
  for (const col of allCollections){
    console.log(`- ${col}`);
  }
}

例8-4 戻されるコレクションのリストのフィルタリング

この例では、ユーザー定義文字列startWithで始まるコレクションの名前のみを出力することによって、getCollectionNames()の結果を制限します。

export function printSomeCollectionNames(numHits, startWith) {

  // loop over all collections in the current schema, limited
  // to those that start with a specific character sequence and
  // a maximum number of hits returned
  const allCollections = soda.getCollectionNames(
    {
      limit: numHits,
      startsWith: startWith
    }
  );
  for (const col of allCollections){
    console.log(`-${col}`);
  }
}

SODA for In-Database JavaScriptによるドキュメント・コレクションの削除

SodaCollection.drop()を使用して、既存のコレクションを削除します。

注意:

SQLを使用して、コレクションの基礎になっているデータベースを削除しないでくださいコレクションを削除することは、そのデータベース表を削除することで終わりにはなりません。表に格納されるドキュメント以外にも、コレクションにはOracle Databaseに永続化されるメタデータも含まれています。コレクションの基礎になっている表を削除しても、コレクション・メタデータは削除されません

ノート:

SODAを使用する通常のアプリケーションを日常的に使用する場合、コレクションをドロップして再作成する必要はありません。しかし、なんらかの理由でこれを行う必要がある場合は、このガイドラインが適用されます。

多少なりともコレクションを使用しているアプリケーションがある場合は、コレクションを削除した後に異なるメタデータで再作成しないでください。すべてのライブSODAオブジェクトが解放されるように、コレクションを再作成する前にこのようなアプリケーションをすべて停止します。

コレクションのドロップだけでは、問題は発生しません。ドロップされたコレクションの読取りまたは書込み操作でエラーが発生します。コレクションをドロップして同じメタデータを持つコレクションを再作成しても、問題はありません。ただし、異なるメタデータでコレクションを再作成して、SODAオブジェクトを使用するライブ・アプリケーションが存在する場合、古いコレクションがアクセスされるというリスクがあり、この場合、エラーは発生しません

SODA for Javaなど、コレクション・メタデータ・キャッシングを許可するSODA実装では、このようなキャッシュが有効になっている場合に、このリスクが高くなります。その場合、コレクションがドロップされても(共有またはローカルの)キャッシュが古いコレクション・オブジェクトのエントリを返す可能性があります。

ノート:

SodaCollection.drop()を使用する前に、コレクションへのすべての書込みをコミットしてください。メソッドが正常に終了するには、コレクションに対してコミットされていないすべての書込みを最初にコミットする必要があります。そうでない場合は、例外が発生します。

例8-5 コレクションの削除

この例は、コレクションを削除する方法を示します。

export function openAndDropCollection(collectionName) {

    // look the collection up
    const col = soda.openCollection(collectionName);
    if (col === null) {
        throw new Error (`No such collection ${collectionName}`);
    }

    // drop the collection - POTENTIALLY DANGEROUS
    col.drop();
}

SODA for In-Database JavaScriptによるドキュメントの作成

SODA for In-Database JavaScriptによるドキュメントの作成について説明します。

SodaDocumentクラスはSODAドキュメントを表します。JSONドキュメントが中心ですが、他のコンテンツ・タイプもサポートされています。SodaDocumentには、実際のドキュメントのコンテンツとメタデータの両方が格納されます。

設計上、JavaScriptは特にJSONとの連携に適しているため、これが他のプログラミング言語よりも優位になっています。

次に、単純なJSONドキュメントの例を示します。

// Create a JSON document (based on the HR.EMPLOYEES table for employee 100)
const doc = {
    "_id": 100,
    "job_id": "AD_PRES",
    "last_name": "King",
    "first_name": "Steven",
    "email": "SKING",
    "manager_id": null,
    "department_id": 90
};

ノート:

SODAでは、JSONコンテンツがRFC 4627に準拠する必要があります。

SodaDocumentオブジェクトは、次の3つの方法で作成できます:

  • sodaDatabase.createDocument()の結果として。これは、SODAの挿入および置換メソッドに使用できる元のSodaDocumentオブジェクトです。SodaDocumentには、コンテンツおよびメディア・タイプのコンポーネントが設定されます。
  • sodaOperation.getOne()のコールなどのデータベースからの読取り操作、またはsodaOperation.getCursor()コール後のsodaDocumentCursor.getNext()からの読取り操作の結果として。これらは、ドキュメントのコンテンツおよび属性(メディア・タイプなど)を含む完全なSodaDocumentオブジェクトを戻します。
  • sodaCollection.insertOneAndGet()sodaOperation.replaceOneAndGet()またはsodaCollection.insertManyAndGet()メソッドの結果として。これらは、ドキュメント・コンテンツ自体を除くすべての属性を含むSodaDocumentsを戻します。システム生成キーなどのドキュメント属性や、新規および更新されたバージョンのドキュメントを検索する場合に便利です。

ドキュメントには次のコンポーネントがあります。

  • キー

  • コンテンツ

  • バージョン

  • メディア・タイプ(JSONドキュメントの場合、"application/json")

ドキュメントのコンテンツは、アプリケーションが格納する必要がある情報を表すすべてのフィールドと_idフィールドで構成されます。このフィールドは、ユーザーが指定するか、省略した場合はOracleによって挿入されます。省略すると、Oracleは長さが12バイトのランダムな値を追加します。

ドキュメントのキーは、ドキュメントの_id列の16進エンコード表現です。これは自動的に計算され、変更できません。このキーは、key()およびkeys(...)メソッドを使用して、検索、置換および削除などの操作を構築する場合によく使用されます。これらの操作については、後の項で説明します。

例8-6 SODAドキュメントの作成

export function createJSONDoc() {
    
  // define the document's contents
  const payload = {
    "_id ": 100,
    "job_id": "AD_PRES",
    "last_name": "King",
    "first_name": "Steven",
    "email": "SKING",
    "manager_id": null,
    "department_id": 90
  };

  // Create a SODA document.
  // Notice that neither key nor version are populated. They will be as soon
  // as the document is inserted into a collection and retrieved.
  const doc = soda.createDocument(payload);
  console.log(`
    ------------------------------------------
    SODA Document using default key
    content (select fields):
    - _id          ${doc.getContent()._id}
    - job_id       ${doc.getContent().job_id}
    - first name   ${doc.getContent().first_name}
    media type:    ${doc.mediaType}
    version   :    ${doc.version}
    key            ${doc.key}`
  );
}

この例にあるようなSodaDocumentインスタンスの作成は、標準ではなく例外です。ほとんどの場合、開発者はSodaCollection.insertOne()またはSodeCollection.insertOneAndGet()を使用します。SodaCollection.insertOne()の使用は、例8-7に示されています。sodaCollection.insertMany()を使用して複数のドキュメントを作成できます。

SODA for In-Database JavaScriptによるコレクションへのドキュメントの挿入

SodaCollection.insertOne()または関連コール(sodaCollection.insertOneAndGet()など)を使用すると、ドキュメントをコレクションに追加できます。コレクションがクライアント割当てキーで構成されておらず、入力ドキュメントがキーを指定していない場合、これらのメソッドでは、ドキュメント・キーが自動的に作成されます。これは、必須ユーザーにはお薦めしません。

SodaCollection.insertOne()は単にドキュメントをコレクションに挿入しますが、SodaCollection.insertOneAndGet()はさらに結果ドキュメントを戻します。結果ドキュメントには、ドキュメント・キーおよびその他の生成されたドキュメント・コンポーネントが含まれます。ただし、実際のドキュメントのコンテンツは除きます(これはパフォーマンス向上のために行われます)。

コレクションがカスタム・メタデータで作成されていないかぎり、どちらの方法でもドキュメントのバージョンが自動的に設定されます。カスタム・メタデータには、一部のデフォルト・メタデータが含まれていない場合があります。コレクションで定義されていない属性を問い合せると、NULL値が戻されます。

ノート:

例外を発生させるのではなく、入力ドキュメントで既存のドキュメントを置き換える場合は、「SODA for In-Database JavaScriptによるコレクションへのドキュメントの保存」を参照してください。

例8-7 コレクションへのSODAドキュメントの挿入

この例では、SodaCollection.insertOne()を使用してコレクションにドキュメントを挿入する方法を示します。

export function insertOneExample() {

  // define the document's contents
  const payload = {
    "_id": 100,
    "job_id": "AD_PRES",
    "last_name": "King",
    "first_name": "Steven",
    "email": "SKING",
    "manager_id": null,
    "department_id": 90
  };

  // create or open the collection to hold the document
  const col = soda.createCollection("MyCollection");

  col.insertOne(payload);
}

例8-8 コレクションへのドキュメントの配列の挿入

この例では、SodaCollection.insertMany()を使用して、1つのコマンドで複数のドキュメントを挿入する方法を示します。この例では、基本的にリレーショナル表HR.employeesをコレクションに変換します。

export function insertManyExample() {

  // select all records from the hr.employees table into an array
  // of JavaScript objects in preparation of a call to insertMany
  const result = session.execute(
    `SELECT
       employee_id "_id",
       first_name "firstName",
       last_name "lastName",
       email "email",
       phone_number "phoneNumber",
       hire_date "hireDate",
       job_id "jobId",
       salary "salary",
       commission_pct "commissionPct",
       manager_id "managerId",
       department_id "departmentId"
     FROM
       hr.employees`,
     [],
     { outFormat: oracledb.OUT_FORMAT_OBJECT }
  );

  // create the collection and insert all employee records
  collection = soda.createCollection('employeesCollection');
  collection.insertMany(result.rows);

  // the MLE JavaScript SQL driver does not auto-commit
  session.commit();
}

SODA for In-Database JavaScriptによるコレクションへのドキュメントの保存

SodaCollection.save()およびsaveAndGet()を使用して、ドキュメントをコレクションに保存します。

これらのメソッドは、メソッドinsertOne()およびinsertOneAndGet()と似ていますが、コレクションがクライアント割当てドキュメント・キーで構成されていて、入力ドキュメントがコレクション内のドキュメントをすでに識別するキーを提供している場合、入力ドキュメントが既存のドキュメントを置き換える点が異なります。これに対し、メソッドinsertOne()およびinsertOneAndGet()は、そのような場合に例外をスローします。

SODA for In-Database JavaScriptの読取りおよび書込み操作

読取りおよび書込み操作(挿入および保存以外)を指定する主な方法は、SodaOperationクラスが提供するメソッドを使用することです。SodaOperationメソッドをつなげて、コレクションに対する読取りおよび書込み操作を指定できます。

非ターミナルSodaOperationメソッドは、呼び出された同じオブジェクトを戻し、メソッドをつなげることができるようにします。

ターミナルSodaOperationメソッドは常に、操作を実行するメソッド・チェーンの最後に出現します。

ノート:

SodaOperationオブジェクトは内部オブジェクトです。そのプロパティは直接変更しないでください。

メソッドのSODAドキュメントに異なる記述がなければ、すべての非ターミナル・メソッドをつなぎ、ターミナル・メソッドでチェーンを終了できます。ただし、すべての組合せに意味があるわけではありません。たとえば、keys()などのドキュメントを一意に識別しないメソッドとversion()メソッドをつなげても意味はありません。

表8-1 読取り操作用の非ターミナル・メソッドの概要

メソッド 説明
key() 指定したドキュメント・キーを持つドキュメントを検索します。
keys() 指定した複数のドキュメント・キーを持つ複数のドキュメントを検索します。
filter() フィルタ仕様(JSONで表される例による問合せ)と一致するドキュメントを検索します。
version() 指定したバージョンのドキュメントを検索します。通常、これはkey()とともに使用されます。
headerOnly() 結果からドキュメント・コンテンツを除外します。
skip() 結果内で指定した数のドキュメントをスキップします。
limit() 結果ドキュメントの数を指定した数に制限します。

表8-2 読取り操作用のターミナル・メソッドの概要

メソッド 説明
getOne() 最大1つのドキュメントを戻す操作を作成して実行します。たとえば、非ターミナル・メソッドkey()の呼出しを含む操作などです。
getCursor() 読取り操作の結果によってカーソルを取得します。
count() 操作で見つかったドキュメントの数をカウントします。
getDocuments() 問合せ基準に一致するドキュメントの配列を取得します。

表8-3 書込み操作用のターミナル・メソッドの概要

メソッド 説明
replaceOne() 1つのドキュメントを置き換えます。
replaceOneAndGet() 1つのドキュメントを置き換えて、結果ドキュメントを返します。
remove() ドキュメントをコレクションから削除します。

関連項目:

SODA for In-Database JavaScriptによるコレクション内のドキュメントの検索

コレクション内のドキュメントを検索するには、SodaCollection.find()を呼び出します。SodaOperationオブジェクトが作成されて戻されます。これは、非ターミナル・メソッドおよびターミナル・メソッドを含むメソッド・チェーンを介して使用されます。

問合せを実行するには、SodaOperation.getCursor()を呼び出して、その結果のカーソルを取得します。次に、カーソルを使用して結果リスト内の各ドキュメントにアクセスします。これは、例8-1およびその他の例で示されています。リソースを節約するために、カーソルを必ずクローズすることが重要です。

ただし、これはコレクション内のドキュメントを検索する際の一般的なワークフローではありません。SodaOperationクラスで提供される複数のメソッドをつなげる方が一般的です。

例8-9 キーによるドキュメントの検索

この例は、メソッドfind()key()およびgetOne()を使用してドキュメントをキーで検索する方法を示しています。

export function findDocByKey(searchKey){

  const collectionName = 'MyCollection';

  // open the collection in preparation of a document lookup
  const col = soda.openCollection(collectionName);
  if (col === null){
    throw new Error(`${collectionName} does not exist`);
  }

  try{
    // perform a lookup of a document with the key provided as a 
    // parameter to this function. Keys are like primary keys,
    // the lookup therefore can only return 1 document max
    const doc = col.find()
                   .key(searchKey)
                   .getOne();
    console.log(`
      document found for key ${searchKey}
      contents: ${doc.getContentAsString()}`
    );
  } catch(err){
      throw new Error(
        `error retrieving document with key ${searchKey} (${err})`
      );
  }
}

ノート:

キーは、数値形式の場合でも引用符で囲む必要があります。

指定されたキーの検索に失敗した場合、データベースはORA-01403 (「データが見つかりません。」)例外をスローします。例外は適切に処理することをお薦めします。この例では、エラーが確実に検出され、業界で最もよく知られている方法に従って処理される責任をファンクションのコール元が担っています。

例8-10 複数のキーを使用したドキュメントの検索

この例では、メソッドfind()keys()getCursor()およびgetNext()を使用して、配列で指定された複数のキーを検索します。

この例で使用されているemployeesCollectionの作成方法の詳細は、例8-8を参照してください。

export function findDocByKeys(searchKeys){
  
  if(!Array.isArray(searchKeys)){
    throw new Error('please provide an array of search keys');
  }

  // open a collection in preparation of a document lookup
  const col = soda.openCollection('employeesCollection');
  if (col === null){
    throw new Error('employeesCollection does not exist');
  }

  try{
    // perform a lookup of a set of documents using
    // the "keys" array provided
    const docCursor =
      col.find()
         .keys(searchKeys)
         .getCursor();

    let doc
      while((doc = docCursor.getNext())){
        console.log(`
          document found for key ${doc.key}
          contents: ${doc.getContentAsString()}`
        );
      }
      docCursor.close();
  } catch(err){
      // there is no error thrown if one/all of the keys aren't found
      // this error handler is generic
      throw new Error(
        `error retrieving documents with keys ${searchKeys} (${err})`
      );
  }
}

find()操作は、エラーで失敗するのではなく、単に、コレクションで見つからないキーのデータを返しません。キーが見つからない場合は、何も戻されません。

例8-11 QBEを使用したコレクション内のドキュメントのフィルタリング

この例では、filter()を使用してコレクション内のドキュメントを検索します。非ターミナルのSodaOperation.filter()メソッドは、コレクション内のJSONドキュメントをフィルタする強力な方法を提供し、複雑なドキュメント問合せおよびJSONドキュメントの順序付けを可能にします。フィルタ仕様には、比較、正規表現、論理演算子および空間演算子などを含めることができます。

filterConditionで定義されている検索式は、部門30で働く110より大きい従業員IDを持つすべての従業員に一致します。

この例で使用されているemployeesCollectionの作成方法の詳細は、例8-8を参照してください。

export function findDocByFiltering(){

  // open a collection in preparation of a document
  // lookup. This particular collection contains all the
  // rows from the HR.employees table converted to SODA
  // documents.
  const col = soda.openCollection('employeesCollection');
  if(col === null){
    throw new Error(`employeesCollection does not exist`);
  }

  // find all employees with an employee_id > 100 and
  // last name beginning with M
  const filterCondition = {
    "$and": [
      { "lastName": { "$upper": { "$startsWith": "M" } } },
      { "_id": { "$gt": 100 } }
    ]  
  };

  try{
    
    // perform the lookup operation using the QBE defined earlier
    const docCursor = col.find()
                         .filter(filterCondition)
                         .getCursor();
    let doc;
    while ((doc = docCursor.getNext())){
      console.log(`
        ------------------------------------
        document found matching the search criteria
        - key:           ${doc.key}
        - _id:           ${doc.getContent()._id}
        - name:          ${doc.getContent().lastName}`
      );
    }

    docCursor.close();
  } catch(err){
      throw new Error(`error looking up documents using a QBE: ${err}`);
  }
}

関連項目:

例8-12 ページ区切り問合せでのskip()およびlimit()の使用

行数が過度に大きくなる場合は、ページ分けするか、戻されるドキュメントの数を制限できます。この例では、このような状況でskip()およびlimit()を使用する方法を示します。

この例で使用されているemployeesCollectionの作成方法の詳細は、例8-8を参照してください。

export function paginationExample(){

  // open a collection in preparation of a document
  // lookup. This particular collection contains all the
  // rows from the HR.employees table converted to SODA
  // documents.
  const col = soda.openCollection('employeesCollection');
  if(col === null){
    throw new Error ('employeesCollection does not exist, aborting');
  }

  // find all employees with an employee_id > 100 and
  // last name beginning with E
  const filterCondition = {
    "$and": [
      { "lastName": { "$upper": { "$startsWith": "M" } } },
      { "_id": { "$gt": 100 } }
    ]
  };

  try{

    // perform the lookup operation using the QBE, skipping the first
    // 5 documents and limiting the result set to 10 documents
    const docCursor = 
      col.find()
         .filter(filterCondition)
         .skip(5)
         .limit(10)
         .getCursor();
    let doc;
    while ((doc = docCursor.getNext())){
      console.log(`
        ------------------------------------
        document found matching the search criteria
        - key:           ${doc.key}
        - employee id:   ${doc.getContent().employeeId}`
      );
    }

    docCursor.close();
  } catch(err){
      throw new Error(
        `error looking up documents by QBE (${err})`
      );
  }
}

例8-13 ドキュメント・バージョンの指定

この例では、非ターミナル・メソッドversion()を使用して、特定のドキュメント・バージョンを指定します。これは、書込み操作用のターミナル・メソッドとともに使用する場合、オプティミスティック・ロックの実装に便利です。

この例で使用されているemployeesCollectionの作成方法の詳細は、例8-8を参照してください。

export function versioningExample(searchKey, version){

  // open a collection in preparation of a document
  // lookup. This particular collection contains all the
  // rows from the HR.employees table converted to SODA
  // documents.
  const col = soda.openCollection("employeesCollection");

  try{
    // perform a lookup of a document using the provided key and version
    const doc = col
      .find()
      .key(searchKey)
      .version(version)
      .getOne();
    console.log(`
      document found for key ${doc.key}
      contents: ${doc.getContentAsString()}`
    );
  } catch(err){
      throw new Error(
        `${err} during lookup. Key: ${searchKey}, version: ${version}`
      );
  }
}

SODAでキーとバージョン・タグに一致するドキュメントが見つからない場合は、ORA-01403: データが見つかりません。エラーがスローされます。

例8-14 見つかったドキュメントの数のカウント

この例では、find()filter()およびcount()メソッドを使用して、コレクションで見つかったドキュメントの数をカウントする方法を示します。filter()式は、部門30で働くすべての従業員に結果を制限します。

この例で使用されているemployeesCollectionの作成方法の詳細は、例8-8を参照してください。

export function countingExample(){

  // open a collection in preparation of a document
  // lookup. This particular collection contains all the
  // rows from the HR.employees table converted to SODA
  // documents.
  const col = soda.openCollection("employeesCollection");
  if(col === null){
    throw new Error('employeesCollection does not exist');
  }

  try{

    // perform a lookup operation identifying all employees working
    // in department 30, limiting the result to headers only
    const filterCondition = {"departmentId": 30};
    const numDocs = col.find()
                       .filter(filterCondition)
                       .count();
    console.log(`there are ${numDocs} documents matching the filter`);
  } catch(err){
      throw new Error(
        `No document found in 'employeesCollection' matching the filter`
      );
  }
}

SODA for In-Database JavaScriptによるコレクション内のドキュメントの置換

コレクション内の1つのドキュメントのコンテンツを別のドキュメントのコンテンツで置き換えるには、まずそのキーを使用して、変更するドキュメントを検索します。SodaOperation.key()は非ターミナル操作であるため、コンテンツを置換する最も簡単な方法は、SodaOperation.key()SodaOperation.replaceOne()またはSodaOperation.replaceOneAndGet()につなげることです。

SodaOperation.replaceOne()はドキュメントを置換するのみですが、SodaOperation.replaceOneAndGet()はドキュメントを置換し、コール元にその結果の新しいドキュメントを提供します。

SodaOperation.replace()SodaOperation.save()の違いは、キーがコレクションにまだ存在しない場合、後者では挿入を実行することです。置換操作では、SodaOperation.key()メソッドによる検索で既存のドキュメントを見つける必要があります。

ノート:

一部のバージョン生成メソッドは、ドキュメント・コンテンツのハッシュ値を生成します。このような場合、ドキュメント・コンテンツが変更されないと、バージョンも変更されません。

例8-15 コレクション内のドキュメントの置換および結果ドキュメントの戻し

この例は、コレクション内のドキュメントを置換し、変更されたドキュメントへの参照を戻す方法を示しています。従業員206に100単位の昇給が与えられているとします。SODA APIを使用して、次のように給与を更新できます:

export function replaceExample(){

  // open employeesCollection in preparation of the update
  const col = soda.openCollection('employeesCollection');
  if (col === null){
    throw new Error("'employeesCollection does not exist");
  }

  try{
    // look up employeeId 206 using a QBE and get the document.
    // Since the documents are inserted into the collection based
    // on the HR.employees table, it is certain that there is at
    // most 1 document with employeeId 206
    const employeeDoc = col
                        .find()
                        .filter({"_id": 206})
                        .getOne();

    // get the document's actual contents/payload
    employee = employeeDoc.getContent();

    // currently it is not possible to include the _id together with
    // the replacement payload. This means existing _id must be deleted.
    // The document, once replaced in the collection, will have its
    // _id injected from the target document
    delete employee_id;

    // increase the salary
    employee.salary += 100;

    // save the document back to the collection. Note that you need
    // to provide the document's key rather than a QBE or else an
    // ORA-40734: key for the document to replace must be specified
    // using the key attribute error will be thrown
    const resultDoc = col
                      .find()
                      .key(employeeDoc.key)
                      .replaceOneAndGet(employee);

    // print some metadata (note that content is not returned for
    // performance reasons)
    console.log(`Document updated successfully:
    - key:           ${resultDoc.key}
    - version:       ${resultDoc.version}`);

  } catch(err){
      console.log(`error modifying employee 206's salary: ${err}`);
  }
}

この例で使用されているemployeesCollectionの作成方法の詳細は、例8-8を参照してください。

ノート:

パフォーマンス上の理由から、実際のドキュメントのコンテンツは戻されないため、変更されたコンテンツを読み取ろうとするとエラーが発生します。

SODA for In-Database JavaScriptによるコレクションからのドキュメントの削除

コレクションからのドキュメントの削除は置換と似ています。最初のステップは、検索操作(通常はドキュメントのキーに基づいて、またはSodaOperation.filter()の検索式を使用して)を実行することです。SodaOperation.remove()のコールはターミナル操作です。つまり、チェーンの最後の操作です。

例8-16 ドキュメント・キーによるコレクションからのドキュメントの削除

この例では、ドキュメント・キーが"100"のドキュメントを削除します。

export function removeByKey(searchKey){
  
  // open MyCollection
  const col = soda.openCollection("MyCollection");
  if(col === null){
    throw new Error("'MyCollection' does not exist");
  }

  // perform a lookup of the document about to be removed
  // and ultimately remove it
  const result = col
                 .find()
                 .key(searchKey)
                 .remove();
  if(result.count === 0){
    throw new Error(
      `failed to delete a document with key ${searchKey}`
    );
  }
}

例8-17 フィルタによるコレクションからのJSONドキュメントの削除

この例では、フィルタを使用して、department_id70のJSONドキュメントを削除します。その後、削除されたドキュメントの数を出力します。

export function removeByFilter(){

  // open the collection
  const col = soda.openCollection("MyCollection");
  if(col === null){
    throw new Error("'MyCollection' does not exist");
  }

  // perform a lookup based on a filter expression and remove
  // the documents matching the filter
  const result = col
                 .find()
                 .filter({"_id": 100})
                 .remove();

  console.log(`${result.count} documents deleted`);
}

SODA for In-Database JavaScriptによるコレクション内のドキュメントの索引付け

索引は、NoSQLスタイルのSODA APIとリレーショナル・アプローチのどちらを使用するかに関係なく、データ・アクセスを高速化できます。SODAコレクション内のドキュメントの索引付けには、SodaCollection.createIndex()を使用します。そのIndexSpecパラメータはテキストのJSON索引指定です。

既存の索引は、SodaCollection.dropIndex()を使用して削除できます。

JSON検索索引は、全文問合せおよび非定型構造問合せに使用され、永続的な記録およびJSONデータ・ガイド情報の自動更新に使用されます。

関連項目:

例8-18 SODA for In-Database JavaScriptを使用したJSONフィールドに対するBツリー索引の作成

この例では、コレクションemployeesCollection (例8-8で作成)内のJSONドキュメントの数値フィールドdepartment_idに対して一意ではないBツリー索引を作成します。

export function createBTreeIndex(){

  // open the collection
  const col = soda.openCollection('employeesCollection');
  if(col === null){
    throw new Error("'employeesCollection' does not exist");
  }

  // define the index...
  const indexSpec = {
    "name": "DEPARTMENTS_IDX",
    "fields": [
      {
        "path": "departmentId",
        "datatype": "number",
        "order": "asc"
      }
    ]
  };

  //... and create it
  try{
    col.createIndex(indexSpec);
  } catch(err){
      throw new Error(
        `could not create the index: ${err}`
      )
  }
}

例8-19 SODA for In-Database JavaScriptを使用したJSON検索索引の作成

この例では、コレクションemployeesCollection (例8-8で作成)のドキュメントを索引付けするためのJSON検索索引の作成方法を示します。これは、非定型問合せおよび全文検索(QBE演算子$containsを使用する問合せ)に使用できます。これによって、JSONドキュメントに関するデータ・ガイド情報(集計構造情報および型情報)が自動的に蓄積および更新されます。索引仕様には、フィールドnameのみが含まれます(例8-18のBツリー索引とは異なり、fieldフィールドは含まれません)。

export function createSearchIndex(){

  // open the collection
  const col = soda.openCollection("employeesCollection");
  if(col === null){
    throw new Error("'employeesCollection' does not exist");
  }

  // define the index properties...
  cost indexSpec = {
    "name": "SEARCH_AND_DATA_GUIDE_IDX",
    "dataguide": "on",
    "search_on": "text_value"
  }

  //...and create it
  try{
    col.createIndex(indexSpec);
  } catch(err){
      throw new Error(
        `could not create the search and Data Guide index: ${err}`
      );
  }
}

非定型(検索)の索引付けのみを高速化する場合は、フィールドdataguideに値"off"を指定する必要があります。dataguide索引付け機能が不要な場合は、同じ方法でこれをオフにできます。

例8-20 SODA for In-Database JavaScriptを使用した索引の削除

この例は、SodaCollection.dropIndex()およびforceオプションを使用してコレクションの既存の索引を削除する方法を示しています。

この例で使用されているemployeesCollectionの作成方法の詳細は、例8-8を参照してください。

export function dropIndex(indexName){

  // open the collection
  const col = soda.openCollection("employeesCollection");
  if(col === null){
    throw new Error("'employeesCollection' does not exist");
  }

  // drop the index
  const result = col.dropIndex(indexName, {"force": true});
  if(!result.dropped){
    throw `Could not drop SODA index '${indexName}'`;
  }
}

SodaCollection.dropIndex()は、単一のフィールドdroppedを含む結果オブジェクトを戻します。索引が削除された場合、その値はtrueで、それ以外の場合はfalseです。どちらの場合でもメソッドは正常に終了します。

オプションのパラメータ・オブジェクトをメソッドに指定できます。forcetrueに設定すると、基礎となるOracle Databaseドメイン索引で通常の削除が許可されない場合に、JSON索引の削除が強制されます。

SODA for In-Database JavaScriptを使用したコレクションのデータ・ガイドの取得

データ・ガイドは、一連のJSON文書に含まれる構造および型情報の概要を示します。これらの文書内で使用されているフィールドに関するメタデータを記録します。JSONドキュメントに対する優れたインサイトを提供し、データ・セットの概要把握に役立ちます。

SodaCollection.getDataGuide()を使用してデータ・ガイドを作成できます。SODAでデータ・ガイドを取得するには、コレクションがJSONのみであり、"dataguide"オプションが"on"であるJSON検索索引を持っている必要があります。データ・ガイドは、sodaCollection.getDataGuide()からSodaDocumentのJSONコンテンツとして戻されます。データ・ガイドは現在のコレクションの状態から推測されます。コレクションが大きくなったり、ドキュメントが変更されると、その後にgetDataGuide()がコールされるたびに新しいデータ・ガイドが戻されます。

例8-21 コレクションのデータ・ガイドの生成

この例では、メソッドgetDataGuide()を使用してコレクションemployeesCollection (例8-8で作成)のデータ・ガイドを取得し、メソッドgetContentAsString()を使用してコンテンツを文字列として出力します。

export function createDataGuide(){

  // open the collection
  const col = soda.openCollection('employeesCollection');
  if(col === null){
    throw new Error("'employeesCollection' does not exist");
  }

  // generate a Data Guide (requires the Data Guide index)
  const doc = col.getDataGuide();
  console.log(doc.getContentAsString());
}

データ・ガイドでは、すべてのフィールドとそのデータ型を含む、コレクションに関する興味深いインサイトを提供できます。employeesCollectionのデータ・ガイドは、この章の読者にとってはすでになじみ深い内容である可能性がありますが、不明なJSONドキュメントをこの方法で簡単に分析できます。前のコード・ブロックでは、次のデータ・ガイドが画面に出力されます:

{
  "type": "object",
  "o:length": 1,
  "properties": {
    "_id": {
      "type": "id",
      "o:length": 24,
      "o:preferred_column_name": "DATA$_id"
    },
    "email": {
      "type": "string",
      "o:length": 16,
      "o:preferred_column_name": "DATA$email"
    },
    "jobId": {
      "type": "string",
      "o:length": 16,
      "o:preferred_column_name": "DATA$jobId"
    },
    "salary": {
      "type": "number",
      "o:length": 8,
      "o:preferred_column_name": "DATA$salary"
    },
    "hireDate": {
      "type": "string",
      "o:length": 32,
      "o:preferred_column_name": "DATA$hireDate"
    },
    "lastName": {
      "type": "string",
      "o:length": 16,
      "o:preferred_column_name": "DATA$lastName"
    },
    "firstName": {
      "type": "string",
      "o:length": 16,
      "o:preferred_column_name": "DATA$firstName"
    },
    "managerId": {
      "type": "string",
      "o:length": 4,
      "o:preferred_column_name": "DATA$managerId"
    },
    "employeeId": {
      "type": "number",
      "o:length": 4,
      "o:preferred_column_name": "DATA$employeeId"
    },
    "phoneNumber": {
      "type": "string",
      "o:length": 16,
      "o:preferred_column_name": "DATA$phoneNumber"
    },
    "departmentId": {
      "type": "string",
      "o:length": 4,
      "o:preferred_column_name": "DATA$departmentId"
    },
    "commissionPct": {
      "type": "string",
      "o:length": 32,
      "o:preferred_column_name": "DATA$commissionPct"
    }
  }
}

SODA for In-Database JavaScriptによるトランザクションの処理

クライアント側のJavaScript SQLドライバとは異なり、MLE JavaScript SQLドライバはautoCommit機能を提供していません。モジュール・コールの場合はPL/SQLレイヤーで、またはconnection.commit()connection.rollback()をコールすることでJavaScriptコードで直接的に、トランザクションをコミットまたはロールバックする必要があります。

注意:

コミットされていない操作でエラーが発生し、トランザクションを明示的にロールバックしない場合、不完全なトランザクションは、関連データを一貫性のない状態(コミットされていない部分的な結果)のままにする場合があります。

SODA APIに関連するコール仕様の作成

この章の前半の「SODA for In-Database JavaScriptの開始」の項で、インライン・コール仕様を使用してMLE SODA APIを呼び出す方法の例を示します。次の短い例は、MLEモジュールでSODAを使用する方法を示しています。

Example 8-22 SODA for In-Database JavaScriptの使用

この例で使用されているemployeesCollectionの作成方法の詳細は、例8-8を参照してください。

CREATE OR REPLACE MLE MODULE end_to_end_demo
LANGUAGE JAVASCRIPT AS

/**
 * Example for a private function used to open and return a SodaCollection
 *
 * @param {string} collectionName the name of the collection to open
 * @returns {SodaCollection} the collection handle
 * @throws Error if the collection cannot be opened
 */
function openAndCheckCollection(collectionName){
  
  const col = soda.openCollection(collectionName);
  if(col === null){
    throw new Error(`invalid collection name: ${collectionName}`);
  }

  return col;
}

/**
 * Top-level (public) function demonstrating how to use a QBE to
 * filter documents in a collection.
 *
 * @param {number} departmentId the numeric department ID
 * @returns {number} the number of employees found in departmentId
 */
export function simpleSodaDemo(departmentId){
  
  if(departmentId === undefined || isNaN(departmentId)){
    throw new Error('please provide a valid numeric department ID');
  }

  const col = openAndCheckCollection('employeesCollection');

  const numDocs = col.find()
                     .filter({"departmentId": departmentId})
                     .count();

  return numDocs;
}
/

モジュールの作成後、コール仕様を作成する必要があります。モジュールは単一のパブリック関数を備えているため、スタンドアロン関数で十分です:

CREATE OR REPLACE FUNCTION simple_soda_demo(
  "departmentId" NUMBER
) RETURN NUMBER
AUTHID current_user
AS MLE MODULE end_to_end_demo
SIGNATURE 'simpleSodaDemo';
/

これで、関数を呼び出すためのすべてが整いました:

select simple_soda_demo(30);

結果:

SIMPLE_SODA_DEMO(30)
--------------------
                   6