Oracle Database 23aiで追加されたMLE JavaScript SODA APIを試してみた

Oracle Database 23aiではMultilingual Engine(以下MLE)のJavaScriptに関する機能が大幅に強化されています。
今回はその中でもSODA APIと呼ばれる機能について検証してみました。
ちなみにSODAとは Simple Oracle Document Access の略語で、NoSQLのように、JSON等ドキュメントのデータをSQLではなくAPI経由で管理できるようにする機能です。
23aiで追加されたMLE JavaScript SODA APIにより、MLE JavaScript内でもSODAの機能を使えるようになりました。
なおMLE JavaScriptの基本については以下の記事をご参照ください。
[Oracle Database 23aiで追加されたMLE JavaScriptの基本的な使い方紹介](https://qiita.com/ssfujita/items/5651d35ebbf0d27e6dc1)
またSODAについては以下のマニュアルをご参照ください。
[Simple Oracle Document Access (SODA)の概要](https://docs.oracle.com/cd/F19136_01/adsdi/overview-soda.html#GUID-BE42F8D3-B86B-43B4-B2A3-5760A4DF79FB)
MLE JavaScriptにおけるSODAの利用法については以下マニュアルをご参照ください。
[Working with SODA Collections in MLE JavaScript Code](https://docs.oracle.com/en/database/oracle/oracle-database/23/mlejs/soda-collections-in-mle-js.html#GUID-58C2BBD9-837D-41CD-A9EF-4EA062F1E1E2)

## 事前準備

必要な権限を付与しておきます。
上述した別Qiita記事の権限に加え、ロールとして以下の付与が必要です。

```sql
GRANT SODA_APP TO <ユーザ名>;
```

## Collectionの新規作成

SODAはまずCollectionと呼ばれるオブジェクトを作成し、その中にDocumentを格納します。
ということで手始めにCollectionを作成するJavaScriptファンクションをMLE Moduleに作成します。
なおこれ以降に作成するJavaScriptファンクションは下記MLE Module「manage_soda_mod」に追記する形式をとります。

```sql
CREATE OR REPLACE MLE MODULE manage_soda_mod 
LANGUAGE JAVASCRIPT AS

import oracledb from "mle-js-oracledb";

// Collectionの一覧を確認
// Collection作成
// Collectionの一覧を確認
export function createCollection(collectionName) {

    // Oracle Databaseに接続
    const connection = oracledb.defaultConnection();

    // SODAオブジェクト作成
    const db = connection.getSodaDatabase();

    // Collection一覧を取得
    let allCollections = db.getCollectionNames();
    for (const col of allCollections) {
        console.log(`- ${col}`);
    }

    //Collectionを新規作成
    // This creates a database table, also named "MyJSONCollection",
    // to store the collection. If a collection with the same name
    // exists it will be opened
    const col = db.createCollection(collectionName);

    // Collection一覧を取得
    allCollections = db.getCollectionNames();
    for (const col of allCollections) {
        console.log(`- ${col}`);
    }

    // commit open transactions
    connection.commit();
}
/
```

以下が対応するMLE Module Callです。

```sql
CREATE OR REPLACE PROCEDURE create_collection_proc(collection_name varchar2)
AUTHID CURRENT_USER AS MLE MODULE manage_soda_mod
SIGNATURE 'createCollection(string)';
/
```

MLE Module Call作成時ですが、私の環境では AUTHID CURRENT_USER 句がないとCall実行時にエラーとなりましたのでご注意ください(以降も同様)。
Callの実行結果は以下のようになります。

```sql
SQL> SET SERVEROUTPUT ON
SQL> EXEC create_collection_proc('HRCollection');
- HRCollection
```

## Documentの追加

先程作成したCollectionにDocumentを追加します。
対応するJavaScriptファンクションは以下の通りです。

```js
export function insertOneDoc(collectionName) {
    // Oracle Databaseに接続
    const connection = oracledb.defaultConnection();
    // SODAオブジェクト作成
    const db = connection.getSodaDatabase();
    // SODA Collectionを新規作成
    const col = db.openCollection(collectionName);

    // 新規追加するJSON Documentを作成
    const doc = {
        "employee_id": 100,
        "job_id": "AD_PRES",
        "last_name": "King",
        "first_name": "Steven",
        "email": "SKING",
        "manager_id": null,
        "department_id": 90
        };

    // DocumentをCollectionに挿入
    col.insertOne(doc);

    // 繰返し処理によってCollection内の各Documentを取得するためにカーソルを使用
    const c = col.find().getCursor();
    let resultDoc;
    while (resultDoc = c.getNext()) {
        const content = resultDoc.getContent();
        console.log(`
            ------------------------------------------
            key:           ${resultDoc.key}
            content (select fields):
            - employee_id  ${content.employee_id}
            - job_id       ${content.job_id}
            - name         ${content.first_name} ${content.last_name}
            version:       ${resultDoc.version}
            last modified: ${resultDoc.lastModified}
            created on:    ${resultDoc.createdOn}
            media type:    ${resultDoc.mediaType}`
        );
    }
    // SODA Documentのカーソルをクローズ
    c.close();
    // トランザクションをcommit
    connection.commit();
}
```

今回は insertOne というファンクションを使い単一のDocumentを追加しましたが、
insertMany というファンクションを使えば一度に複数のDocumentを追加することも可能です。

以下が対応するMLE Module Callです。

```sql
CREATE OR REPLACE PROCEDURE insert_one_doc_proc(collection_name varchar2)
AUTHID CURRENT_USER AS MLE MODULE manage_soda_mod
SIGNATURE 'insertOneDoc(string)';
/
```

Callの実行結果は以下のようになります。

```sql
SQL> SET SERVEROUTPUT ON
SQL> EXEC insert_one_doc_proc('HRCollection');
------------------------------------------
key:       0E83C362A0484F73BFC33E5C972C289F
content (select fields):
- employee_id  100
- job_id       AD_PRES
- name       Steven King
version:       B6E4346FE9284F52BFD52964EAC8564E
last modified: 2023-08-14T14:38:24.118483Z
created on:    2023-08-14T14:38:24.118483Z
media type:    application/json
```

## 特定Documentの削除

Collectionから特定Documentを削除します。
対応するJavaScriptファンクションは以下の通りです。

```js
export function removeDoc(collectionName, employeeId) {
    // Oracle Databaseに接続
    const connection = oracledb.defaultConnection();
    // SODAオブジェクト作成
    const db = connection.getSodaDatabase();
    // SODA Collectionを新規作成
    const col = db.openCollection(collectionName);

    // 繰返し処理によってCollection内の各Documentを取得するためにカーソルを使用
    const c = col.find().getCursor();
    let resultDoc;
    while (resultDoc = c.getNext()) {
        const content = resultDoc.getContent();
        console.log(`
            ------------------------------------------
            key:           ${resultDoc.key}
            content (select fields):
            - employee_id  ${content.employee_id}
            - job_id       ${content.job_id}
            - name         ${content.first_name} ${content.last_name}
            version:       ${resultDoc.version}
            last modified: ${resultDoc.lastModified}
            created on:    ${resultDoc.createdOn}
            media type:    ${resultDoc.mediaType}`
        );
    }
    // SODA Documentのカーソルをクローズ
    c.close();
    
    // Document削除
    const result = col.find().filter({"employee_id": employeeId}).remove();

    // 繰返し処理によってCollection内の各Documentを取得するためにカーソルを使用
    const c2 = col.find().getCursor();
    console.log('==\n\nafter removing doc\n==');
    while (resultDoc = c2.getNext()) {
        const content = resultDoc.getContent();
        console.log(`
            ------------------------------------------
            key:           ${resultDoc.key}
            content (select fields):
            - employee_id  ${content.employee_id}
            - job_id       ${content.job_id}
            - name         ${content.first_name} ${content.last_name}
            version:       ${resultDoc.version}
            last modified: ${resultDoc.lastModified}
            created on:    ${resultDoc.createdOn}
            media type:    ${resultDoc.mediaType}`
        );
    }
    // SODA Documentのカーソルをクローズ
    c2.close();

    // トランザクションをcommit
    connection.commit();
}
```

以下が対応するMLE Module Callです。

```sql
CREATE OR REPLACE PROCEDURE remove_doc_proc(collection_name varchar2, employee_id number)
AUTHID CURRENT_USER AS MLE MODULE manage_soda_mod
SIGNATURE 'removeDoc(string, number)';
/
```

Callの実行結果は以下のようになります。
employee_id が 102 に対応するDocumentが削除されています。

```sql
SQL> SET SERVEROUTPUT ON
SQL> EXEC remove_doc_proc('HRCollection', 102);
------------------------------------------
key:       0E83C362A0484F73BFC33E5C972C289F
content (select fields):
- employee_id  100
- job_id       AD_PRES
- name       Steven King
version:       B6E4346FE9284F52BFD52964EAC8564E
last modified: 2023-08-14T14:38:24.118483Z
created on:    2023-08-14T14:38:24.118483Z
media type:    application/json
------------------------------------------
key:       D8F9A07A7F394FEFBF4B3A8768DC061E
content (select fields):
- employee_id  101
- job_id       undefined
- name       Neena Kochhar
version:       42596C892CA14FADBF019695CB6FF51F
last modified: 2023-08-14T15:18:45.225486Z
created on:    2023-08-14T15:18:45.225486Z
media type:    application/json
------------------------------------------
key:       8293FA888B6A4F3EBF7046AD4016FB8F
content (select fields):
- employee_id  102
- job_id       undefined
- name       Lex De Haan
version:       F10137FC66CE4FDABF530B72850AAC1D
last modified: 2023-08-14T15:18:45.225486Z
created on:    2023-08-14T15:18:45.225486Z
media type:    application/json
==
after removing doc
==
------------------------------------------
key:       0E83C362A0484F73BFC33E5C972C289F
content (select fields):
- employee_id  100
- job_id       AD_PRES
- name       Steven King
version:       B6E4346FE9284F52BFD52964EAC8564E
last modified: 2023-08-14T14:38:24.118483Z
created on:    2023-08-14T14:38:24.118483Z
media type:    application/json
------------------------------------------
key:       D8F9A07A7F394FEFBF4B3A8768DC061E
content (select fields):
- employee_id  101
- job_id       undefined
- name       Neena Kochhar
version:       42596C892CA14FADBF019695CB6FF51F
last modified: 2023-08-14T15:18:45.225486Z
created on:    2023-08-14T15:18:45.225486Z
media type:    application/json
```

## 既存Collectionの削除

最後にCollectionを削除します。
対応するJavaScriptファンクションは以下の通りです。

```js
export function dropCol(collectionName) {
    // Oracle Databaseに接続
    const connection = oracledb.defaultConnection();
    // SODAオブジェクト作成
    const db = connection.getSodaDatabase();
    // SODA Collectionを新規作成
    const col = db.openCollection(collectionName);

    // Collection一覧を取得
    let allCollections = db.getCollectionNames();
    console.log('before drop collection');
    for (const col of allCollections) {
        console.log(`- ${col}`);
    }

    // Collectionを削除
    col.drop();

    // Collection一覧を取得
    allCollections = db.getCollectionNames();
    console.log('after drop collection');
    for (const col of allCollections) {
        console.log(`- ${col}`);
    }

    // トランザクションをcommit
    connection.commit();
}
```

以下が対応するMLE Module Callです。

```sql
CREATE OR REPLACE PROCEDURE drop_col_proc(collection_name varchar2)
AUTHID CURRENT_USER AS MLE MODULE manage_soda_mod
SIGNATURE 'dropCol(string)';
/
```

Callの実行結果は以下のようになります。
既存であった HRCollection というCollectionが削除されました。

```sql
SQL> SET SERVEROUTPUT ON
SQL> EXEC drop_col_proc('HRCollection');
before drop collection
- HRCollection
after drop collection
```

以上が基本的な使用例となります。
MLE JavaScriptでSODAをご利用する際の参考になりましたら幸いです。