

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

# Amazon QLDB Node.js チュートリアル
<a name="getting-started.nodejs.tutorial"></a>

**重要**  
サポート終了通知: 既存のお客様は、07/31/2025 のサポート終了まで Amazon QLDB を使用できます。詳細については、[「Amazon QLDB 台帳を Amazon Aurora PostgreSQL に移行する](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)」を参照してください。

チュートリアルサンプルアプリケーションのこの実装では、Amazon QLDB ドライバーと AWS SDK for JavaScript in Node.js を使用して QLDB 台帳を作成し、サンプルデータを入力します。

このチュートリアルを進めるときは、管理 API オペレーションについて「[AWS SDK for JavaScript API リファレンス](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/)」を参照できます。トランザクションデータのオペレーションについては、「[Node.js 用 QLDB ドライバー API リファレンス](https://amazon-qldb-docs.s3.amazonaws.com/drivers/nodejs/3.1.0/index.html)」を参照してください。

**注記**  
該当する場合、一部のチュートリアルステップでは、サポートされているメジャーバージョンの Node.js 用 QLDB ドライバーに対して異なるコマンドまたはコード例があります。

**Topics**
+ [

# Amazon QLDB Node.js サンプルアプリケーションのインストール
](sample-app.nodejs.md)
+ [

# ステップ 1: 新しい台帳を作成する
](getting-started.nodejs.step-1.md)
+ [

# ステップ 2: 台帳への接続をテストする
](getting-started.nodejs.step-2.md)
+ [

# ステップ 3: テーブル、インデックス、およびサンプルデータを作成する
](getting-started.nodejs.step-3.md)
+ [

# ステップ 4: 台帳のテーブルにクエリを実行する
](getting-started.nodejs.step-4.md)
+ [

# ステップ 5: 台帳内のドキュメントを変更する
](getting-started.nodejs.step-5.md)
+ [

# ステップ 6: ドキュメントのリビジョン履歴を表示する
](getting-started.nodejs.step-6.md)
+ [

# ステップ 7: 台帳内のドキュメントを検証する
](getting-started.nodejs.step-7.md)
+ [

# ステップ 8 (オプション): リソースをクリーンアップする
](getting-started.nodejs.step-8.md)

# Amazon QLDB Node.js サンプルアプリケーションのインストール
<a name="sample-app.nodejs"></a>

**重要**  
サポート終了通知: 既存のお客様は、07/31/2025 のサポート終了まで Amazon QLDB を使用できます。詳細については、[「Amazon QLDB 台帳を Amazon Aurora PostgreSQL に移行する](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)」を参照してください。

このセクションでは、ステップバイステップの Node.js のチュートリアル用に提供されている Amazon QLDB サンプルアプリケーションをインストールして実行する方法について説明します。このサンプルアプリケーションのユースケースは、車両登録に関する完全な履歴情報を追跡する自動車部門 (DMV) データベースです。

Node.js 用の DMV サンプルアプリケーションは、GitHub リポジトリ [aws-samples/amazon-qldb-dmv-sample-nodejs](https://github.com/aws-samples/amazon-qldb-dmv-sample-nodejs) でオープンソースとして公開されています。

## 前提条件
<a name="sample-app.nodejs.prereqs"></a>

開始する前に、Node.js 用 QLDB ドライバーの「[前提条件](getting-started.nodejs.md#getting-started.nodejs.prereqs)」を完了していることを確認します。これには、Node.js をインストールし、次の操作を行うことが含まれます。

1. にサインアップします AWS。

1. QLDB の適切なアクセス許可を持つユーザーを作成します。

1. 開発に必要なプログラムへのアクセスを提供します。

このチュートリアルのすべての手順を完了するには、QLDB API を介して台帳リソースへのフル管理アクセス権が必要です。

## インストール
<a name="sample-app.nodejs.install"></a>

**サンプルアプリケーションをインストールするには**

1. 次のコマンドを入力して、GitHub からサンプルアプリケーションのクローンを作成します。

------
#### [ 2.x ]

   ```
   git clone https://github.com/aws-samples/amazon-qldb-dmv-sample-nodejs.git
   ```

------
#### [ 1.x ]

   ```
   git clone -b v1.0.0 https://github.com/aws-samples/amazon-qldb-dmv-sample-nodejs.git
   ```

------

   サンプルアプリケーションは、このチュートリアルの完全なソースコードとその依存関係 (Node.js ドライバーや [AWS SDK for JavaScript in Node.js](https://aws.amazon.com/sdk-for-node-js) を含む) をパッケージ化しています。このアプリケーションは、TypeScript で書かれています。

1. `amazon-qldb-dmv-sample-nodejs` パッケージのクローンが作成されているディレクトリに切り替えます。

   ```
   cd amazon-qldb-dmv-sample-nodejs
   ```

1. 依存関係のクリーンインストールを実行します。

   ```
   npm ci
   ```

1. パッケージをトランスパイルします。

   ```
   npm run build
   ```

   トランスパイルされた JavaScript ファイルが、`./dist` ディレクトリに書き込まれます。

1. [ステップ 1: 新しい台帳を作成する](getting-started.nodejs.step-1.md) に進み、チュートリアルを開始して台帳を作成します。

# ステップ 1: 新しい台帳を作成する
<a name="getting-started.nodejs.step-1"></a>

**重要**  
サポート終了通知: 既存のお客様は、07/31/2025 のサポート終了まで Amazon QLDB を使用できます。詳細については、[「Amazon QLDB 台帳を Amazon Aurora PostgreSQL に移行する](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)」を参照してください。

このステップでは、`vehicle-registration` という名前の新しい Amazon QLDB 台帳を作成します。

**新しい台帳を作成するには**

1. 次のファイル (`Constants.ts`) を確認します。このファイルには、このチュートリアル内の他のすべてのプログラムで使用される定数値が含まれています。

   ```
   /*
    * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
    * SPDX-License-Identifier: MIT-0
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this
    * software and associated documentation files (the "Software"), to deal in the Software
    * without restriction, including without limitation the rights to use, copy, modify,
    * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    * permit persons to whom the Software is furnished to do so.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
   
   /**
    * Constant values used throughout this tutorial.
    */
   export const LEDGER_NAME = "vehicle-registration";
   export const LEDGER_NAME_WITH_TAGS = "tags";
   
   export const DRIVERS_LICENSE_TABLE_NAME = "DriversLicense";
   export const PERSON_TABLE_NAME = "Person";
   export const VEHICLE_REGISTRATION_TABLE_NAME = "VehicleRegistration";
   export const VEHICLE_TABLE_NAME = "Vehicle";
   
   export const GOV_ID_INDEX_NAME = "GovId";
   export const LICENSE_NUMBER_INDEX_NAME = "LicenseNumber";
   export const LICENSE_PLATE_NUMBER_INDEX_NAME = "LicensePlateNumber";
   export const PERSON_ID_INDEX_NAME = "PersonId";
   export const VIN_INDEX_NAME = "VIN";
   
   export const RETRY_LIMIT = 4;
   
   export const JOURNAL_EXPORT_S3_BUCKET_NAME_PREFIX = "qldb-tutorial-journal-export";
   export const USER_TABLES = "information_schema.user_tables";
   ```

1. 次のプログラム (`CreateLedger.ts`) を使用して、`vehicle-registration` という名前の台帳を作成します。

   ```
   /*
    * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
    * SPDX-License-Identifier: MIT-0
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this
    * software and associated documentation files (the "Software"), to deal in the Software
    * without restriction, including without limitation the rights to use, copy, modify,
    * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    * permit persons to whom the Software is furnished to do so.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
   
   import { QLDB } from "aws-sdk";
   import {
       CreateLedgerRequest,
       CreateLedgerResponse,
       DescribeLedgerRequest,
       DescribeLedgerResponse
   } from "aws-sdk/clients/qldb";
   
   import { LEDGER_NAME } from "./qldb/Constants";
   import { error, log } from "./qldb/LogUtil";
   import { sleep } from "./qldb/Util";
   
   const LEDGER_CREATION_POLL_PERIOD_MS = 10000;
   const ACTIVE_STATE = "ACTIVE";
   
   /**
    * Create a new ledger with the specified name.
    * @param ledgerName Name of the ledger to be created.
    * @param qldbClient The QLDB control plane client to use.
    * @returns Promise which fulfills with a CreateLedgerResponse.
    */
   export async function createLedger(ledgerName: string, qldbClient: QLDB): Promise<CreateLedgerResponse> {
       log(`Creating a ledger named: ${ledgerName}...`);
       const request: CreateLedgerRequest = {
           Name: ledgerName,
           PermissionsMode: "ALLOW_ALL"
       }
       const result: CreateLedgerResponse = await qldbClient.createLedger(request).promise();
       log(`Success. Ledger state: ${result.State}.`);
       return result;
   }
   
   /**
    * Wait for the newly created ledger to become active.
    * @param ledgerName Name of the ledger to be checked on.
    * @param qldbClient The QLDB control plane client to use.
    * @returns Promise which fulfills with a DescribeLedgerResponse.
    */
   export async function waitForActive(ledgerName: string, qldbClient: QLDB): Promise<DescribeLedgerResponse> {
       log(`Waiting for ledger ${ledgerName} to become active...`);
       const request: DescribeLedgerRequest = {
           Name: ledgerName
       }
       while (true) {
           const result: DescribeLedgerResponse = await qldbClient.describeLedger(request).promise();
           if (result.State === ACTIVE_STATE) {
               log("Success. Ledger is active and ready to be used.");
               return result;
           }
           log("The ledger is still creating. Please wait...");
           await sleep(LEDGER_CREATION_POLL_PERIOD_MS);
       }
   }
   
   /**
    * Create a ledger and wait for it to be active.
    * @returns Promise which fulfills with void.
    */
   const main = async function(): Promise<void> {
       try {
           const qldbClient: QLDB = new QLDB();
           await createLedger(LEDGER_NAME, qldbClient);
           await waitForActive(LEDGER_NAME, qldbClient);
       } catch (e) {
           error(`Unable to create the ledger: ${e}`);
       }
   }
   
   if (require.main === module) {
       main();
   }
   ```
**注記**  
`createLedger` の呼び出しで、台帳名とアクセス許可モードを指定する必要があります。台帳データのセキュリティを最大化する、`STANDARD` のアクセス許可モードの使用を推奨します。
台帳を作成すると、デフォルトで*削除保護*が有効になります。これは QLDB の機能であり、ユーザーによって台帳が削除されるのを防ぎます。QLDB API または AWS Command Line Interface () を使用して、台帳の作成時に削除保護を無効にするオプションがありますAWS CLI。
必要に応じて、台帳にアタッチするタグを指定することもできます。

1. トランスパイルされたプログラムを実行するには、次のコマンドを入力します。

   ```
   node dist/CreateLedger.js
   ```

新しい台帳への接続を確認するには、「[ステップ 2: 台帳への接続をテストする](getting-started.nodejs.step-2.md)」に進みます。

# ステップ 2: 台帳への接続をテストする
<a name="getting-started.nodejs.step-2"></a>

**重要**  
サポート終了通知: 既存のお客様は、07/31/2025 のサポート終了まで Amazon QLDB を使用できます。詳細については、[「Amazon QLDB 台帳を Amazon Aurora PostgreSQL に移行する](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)」を参照してください。

このステップでは、トランザクションデータ API エンドポイントを使用して Amazon QLDB の `vehicle-registration` 台帳に接続できることを確認します。

**台帳への接続をテストするには**

1. 次のプログラム (`ConnectToLedger.ts`) を使用して、`vehicle-registration` 台帳へのデータセッション接続を作成します。

------
#### [ 2.x ]

   ```
   /*
    * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
    * SPDX-License-Identifier: MIT-0
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this
    * software and associated documentation files (the "Software"), to deal in the Software
    * without restriction, including without limitation the rights to use, copy, modify,
    * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    * permit persons to whom the Software is furnished to do so.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
   
   import { QldbDriver, RetryConfig  } from "amazon-qldb-driver-nodejs";
   import { ClientConfiguration } from "aws-sdk/clients/qldbsession";
   
   import { LEDGER_NAME } from "./qldb/Constants";
   import { error, log } from "./qldb/LogUtil";
   
   const qldbDriver: QldbDriver = createQldbDriver();
   
   /**
    * Create a driver for creating sessions.
    * @param ledgerName The name of the ledger to create the driver on.
    * @param serviceConfigurationOptions The configurations for the AWS SDK client that the driver uses.
    * @returns The driver for creating sessions.
    */
   export function createQldbDriver(
       ledgerName: string = LEDGER_NAME,
       serviceConfigurationOptions: ClientConfiguration = {}
   ): QldbDriver {
       const retryLimit = 4;
       const maxConcurrentTransactions = 10;
       //Use driver's default backoff function (and hence, no second parameter provided to RetryConfig)
       const retryConfig: RetryConfig = new RetryConfig(retryLimit);
       const qldbDriver: QldbDriver = new QldbDriver(ledgerName, serviceConfigurationOptions, maxConcurrentTransactions, retryConfig);
       return qldbDriver;
   }
   
   export function getQldbDriver(): QldbDriver {
       return qldbDriver;
   }
   
   /**
    * Connect to a session for a given ledger using default settings.
    * @returns Promise which fulfills with void.
    */
   const main = async function(): Promise<void> {
       try {
           log("Listing table names...");
           const tableNames: string[] = await qldbDriver.getTableNames();
           tableNames.forEach((tableName: string): void => {
               log(tableName);
           });
       } catch (e) {
           error(`Unable to create session: ${e}`);
       }
   }
   
   if (require.main === module) {
       main();
   }
   ```

------
#### [ 1.x ]

   ```
   /*
    * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
    * SPDX-License-Identifier: MIT-0
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this
    * software and associated documentation files (the "Software"), to deal in the Software
    * without restriction, including without limitation the rights to use, copy, modify,
    * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    * permit persons to whom the Software is furnished to do so.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
   
   import { QldbDriver  } from "amazon-qldb-driver-nodejs";
   import { ClientConfiguration } from "aws-sdk/clients/qldbsession";
   
   import { LEDGER_NAME } from "./qldb/Constants";
   import { error, log } from "./qldb/LogUtil";
   
   const qldbDriver: QldbDriver = createQldbDriver();
   
   /**
    * Create a driver for creating sessions.
    * @param ledgerName The name of the ledger to create the driver on.
    * @param serviceConfigurationOptions The configurations for the AWS SDK client that the driver uses.
    * @returns The driver for creating sessions.
    */
   export function createQldbDriver(
       ledgerName: string = LEDGER_NAME,
       serviceConfigurationOptions: ClientConfiguration = {}
   ): QldbDriver {
       const qldbDriver: QldbDriver = new QldbDriver(ledgerName, serviceConfigurationOptions);
       return qldbDriver;
   }
   
   export function getQldbDriver(): QldbDriver {
       return qldbDriver;
   }
   
   /**
    * Connect to a session for a given ledger using default settings.
    * @returns Promise which fulfills with void.
    */
   var main = async function(): Promise<void> {
       try {
           log("Listing table names...");
           const tableNames: string[] = await qldbDriver.getTableNames();
           tableNames.forEach((tableName: string): void => {
               log(tableName);
           });
       } catch (e) {
           error(`Unable to create session: ${e}`);
       } 
   }
   
   if (require.main === module) {
       main();
   }
   ```

------
**注記**  
台帳に対してデータトランザクションを実行するには、QLDB ドライバーオブジェクトを作成して、指定した台帳に接続する必要があります。これは、[前のステップ](getting-started.nodejs.step-1.md)で台帳の作成に使用した `qldbClient` オブジェクトとは異なるクライアントオブジェクトです。前のクライアントは、「[Amazon QLDB API リファレンス](api-reference.md)」に示されている管理 API オペレーションの実行にのみ使用されます。

1. トランスパイルされたプログラムを実行するには、次のコマンドを入力します。

   ```
   node dist/ConnectToLedger.js
   ```

`vehicle-registration` 台帳にテーブルを作成するには、「[ステップ 3: テーブル、インデックス、およびサンプルデータを作成する](getting-started.nodejs.step-3.md)」に進みます。

# ステップ 3: テーブル、インデックス、およびサンプルデータを作成する
<a name="getting-started.nodejs.step-3"></a>

**重要**  
サポート終了通知: 既存のお客様は、07/31/2025 のサポート終了まで Amazon QLDB を使用できます。詳細については、[「Amazon QLDB 台帳を Amazon Aurora PostgreSQL に移行する](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)」を参照してください。

Amazon QLDB 台帳がアクティブになり、接続を受け入れると、車両、車両の所有者、登録情報に関するデータのテーブルを作成できるようになります。テーブルとインデックスを作成した後、これらにデータをロードできます。

このステップでは、`vehicle-registration` 台帳に 4 つのテーブルを作成します。
+ `VehicleRegistration`
+ `Vehicle`
+ `Person`
+ `DriversLicense`

以下のインデックスも作成します。


****  

| テーブル名 | フィールド | 
| --- | --- | 
| VehicleRegistration | VIN | 
| VehicleRegistration | LicensePlateNumber | 
| Vehicle | VIN | 
| Person | GovId | 
| DriversLicense | LicenseNumber | 
| DriversLicense | PersonId | 

サンプルデータを挿入するときは、まず `Person` テーブルにドキュメントを挿入します。次に、各 `Person` ドキュメントからシステムによって割り当てられた `id` を使用して、適切な `VehicleRegistration` および `DriversLicense` ドキュメントの対応するフィールドに入力します。

**ヒント**  
ベストプラクティスとして、外部キーには、ドキュメントにシステムによって割り当てられた `id` を使用します。一意の識別子 (車両の VIN など) 用にフィールドを定義することはできますが、実際のドキュメントの一意の識別子はその `id` です。このフィールドはドキュメントのメタデータに含まれており、コミット済みビュー (*テーブルのシステム定義のビュー*) でクエリを実行できます。  
QLDB におけるビューの詳細については、「[重要な概念](ledger-structure.md)」を参照してください。メタデータの詳細については、「[ドキュメントのメタデータのクエリの実行](working.metadata.md)」を参照してください。

**テーブルとインデックスを作成するには**

1. 次のプログラム (`CreateTable.ts`) を使用して前述のテーブルを作成します。

   ```
   /*
    * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
    * SPDX-License-Identifier: MIT-0
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this
    * software and associated documentation files (the "Software"), to deal in the Software
    * without restriction, including without limitation the rights to use, copy, modify,
    * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    * permit persons to whom the Software is furnished to do so.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
   
   import { QldbDriver, Result, TransactionExecutor } from "amazon-qldb-driver-nodejs";
   
   import { getQldbDriver } from "./ConnectToLedger";
   import {
       DRIVERS_LICENSE_TABLE_NAME,
       PERSON_TABLE_NAME,
       VEHICLE_REGISTRATION_TABLE_NAME,
       VEHICLE_TABLE_NAME
   } from "./qldb/Constants";
   import { error, log } from "./qldb/LogUtil";
   
   /**
    * Create multiple tables in a single transaction.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @param tableName Name of the table to create.
    * @returns Promise which fulfills with the number of changes to the database.
    */
   export async function createTable(txn: TransactionExecutor, tableName: string): Promise<number> {
       const statement: string = `CREATE TABLE ${tableName}`;
       return await txn.execute(statement).then((result: Result) => {
           log(`Successfully created table ${tableName}.`);
           return result.getResultList().length;
       });
   }
   
   /**
    * Create tables in a QLDB ledger.
    * @returns Promise which fulfills with void.
    */
   const main = async function(): Promise<void> {
       try {
           const qldbDriver: QldbDriver = getQldbDriver();
           await qldbDriver.executeLambda(async (txn: TransactionExecutor) => {
               Promise.all([
                   createTable(txn, VEHICLE_REGISTRATION_TABLE_NAME),
                   createTable(txn, VEHICLE_TABLE_NAME),
                   createTable(txn, PERSON_TABLE_NAME),
                   createTable(txn, DRIVERS_LICENSE_TABLE_NAME)
               ]);
           });
       } catch (e) {
           error(`Unable to create tables: ${e}`);
       }
   }
   
   if (require.main === module) {
       main();
   }
   ```
**注記**  
このプログラムは、QLDB ドライバーインスタンスで `executeLambda` 関数を使用する方法を示しています。この例では、1 つの Lambda 式で複数の `CREATE TABLE` PartiQL ステートメントを実行します。  
この execute 関数は、暗黙的にトランザクションを開始し、Lambda ですべてのステートメントを実行して、トランザクションを自動コミットします。

1. トランスパイルされたプログラムを実行するには、次のコマンドを入力します。

   ```
   node dist/CreateTable.js
   ```

1. 次のプログラム (`CreateIndex.ts`) を使用して前述のテーブルにインデックスを作成します。

   ```
   /*
    * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
    * SPDX-License-Identifier: MIT-0
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this
    * software and associated documentation files (the "Software"), to deal in the Software
    * without restriction, including without limitation the rights to use, copy, modify,
    * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    * permit persons to whom the Software is furnished to do so.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
   
   import { QldbDriver, TransactionExecutor } from "amazon-qldb-driver-nodejs";
   
   import { getQldbDriver } from "./ConnectToLedger";
   import {
       DRIVERS_LICENSE_TABLE_NAME,
       GOV_ID_INDEX_NAME,
       LICENSE_NUMBER_INDEX_NAME,
       LICENSE_PLATE_NUMBER_INDEX_NAME,
       PERSON_ID_INDEX_NAME,
       PERSON_TABLE_NAME,
       VEHICLE_REGISTRATION_TABLE_NAME,
       VEHICLE_TABLE_NAME,
       VIN_INDEX_NAME
   } from "./qldb/Constants";
   import { error, log } from "./qldb/LogUtil";
   
   /**
    * Create an index for a particular table.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @param tableName Name of the table to add indexes for.
    * @param indexAttribute Index to create on a single attribute.
    * @returns Promise which fulfills with the number of changes to the database.
    */
   export async function createIndex(
       txn: TransactionExecutor, 
       tableName: string, 
       indexAttribute: string
   ): Promise<number> {
       const statement: string = `CREATE INDEX on ${tableName} (${indexAttribute})`;
       return await txn.execute(statement).then((result) => {
           log(`Successfully created index ${indexAttribute} on table ${tableName}.`);
           return result.getResultList().length;
       });
   }
   
   /**
    * Create indexes on tables in a particular ledger.
    * @returns Promise which fulfills with void.
    */
   const main = async function(): Promise<void> {
       try {
           const qldbDriver: QldbDriver = getQldbDriver();
           await qldbDriver.executeLambda(async (txn: TransactionExecutor) => {
               Promise.all([
                   createIndex(txn, PERSON_TABLE_NAME, GOV_ID_INDEX_NAME),
                   createIndex(txn, VEHICLE_TABLE_NAME, VIN_INDEX_NAME),
                   createIndex(txn, VEHICLE_REGISTRATION_TABLE_NAME, VIN_INDEX_NAME),
                   createIndex(txn, VEHICLE_REGISTRATION_TABLE_NAME, LICENSE_PLATE_NUMBER_INDEX_NAME),
                   createIndex(txn, DRIVERS_LICENSE_TABLE_NAME, PERSON_ID_INDEX_NAME),
                   createIndex(txn, DRIVERS_LICENSE_TABLE_NAME, LICENSE_NUMBER_INDEX_NAME)
               ]);
           });
       } catch (e) {
           error(`Unable to create indexes: ${e}`);
       }
   }
   
   if (require.main === module) {
       main();
   }
   ```

1. トランスパイルされたプログラムを実行するには、次のコマンドを入力します。

   ```
   node dist/CreateIndex.js
   ```

**データをテーブルにロードするには**

1. 以下の `.ts` ファイルを確認します。

   1. `SampleData.ts` - `vehicle-registration` テーブルに挿入するサンプルデータが含まれています。

      ```
      /*
       * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
       * SPDX-License-Identifier: MIT-0
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy of this
       * software and associated documentation files (the "Software"), to deal in the Software
       * without restriction, including without limitation the rights to use, copy, modify,
       * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
       * permit persons to whom the Software is furnished to do so.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
       * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
       * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
       * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
       * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
       * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
       */
      
      import { Decimal } from "ion-js";
      
      const EMPTY_SECONDARY_OWNERS: object[] = [];
      export const DRIVERS_LICENSE = [
          {
              PersonId: "",
              LicenseNumber: "LEWISR261LL",
              LicenseType: "Learner",
              ValidFromDate: new Date("2016-12-20"),
              ValidToDate: new Date("2020-11-15")
          },
          {
              PersonId: "",
              LicenseNumber : "LOGANB486CG",
              LicenseType: "Probationary",
              ValidFromDate : new Date("2016-04-06"),
              ValidToDate : new Date("2020-11-15")
          },
          {
              PersonId: "",
              LicenseNumber : "744 849 301",
              LicenseType: "Full",
              ValidFromDate : new Date("2017-12-06"),
              ValidToDate : new Date("2022-10-15")
          },
          {
              PersonId: "",
              LicenseNumber : "P626-168-229-765",
              LicenseType: "Learner",
              ValidFromDate : new Date("2017-08-16"),
              ValidToDate : new Date("2021-11-15")
          },
          {
              PersonId: "",
              LicenseNumber : "S152-780-97-415-0",
              LicenseType: "Probationary",
              ValidFromDate : new Date("2015-08-15"),
              ValidToDate : new Date("2021-08-21")
          }
      ];
      export const PERSON = [
          {
              FirstName : "Raul",
              LastName : "Lewis",
              DOB : new Date("1963-08-19"),
              Address : "1719 University Street, Seattle, WA, 98109",
              GovId : "LEWISR261LL",
              GovIdType : "Driver License"
          },
          {
              FirstName : "Brent",
              LastName : "Logan",
              DOB : new Date("1967-07-03"),
              Address : "43 Stockert Hollow Road, Everett, WA, 98203",
              GovId : "LOGANB486CG",
              GovIdType : "Driver License"
          },
          {
              FirstName : "Alexis",
              LastName : "Pena",
              DOB : new Date("1974-02-10"),
              Address : "4058 Melrose Street, Spokane Valley, WA, 99206",
              GovId : "744 849 301",
              GovIdType : "SSN"
          },
          {
              FirstName : "Melvin",
              LastName : "Parker",
              DOB : new Date("1976-05-22"),
              Address : "4362 Ryder Avenue, Seattle, WA, 98101",
              GovId : "P626-168-229-765",
              GovIdType : "Passport"
          },
          {
              FirstName : "Salvatore",
              LastName : "Spencer",
              DOB : new Date("1997-11-15"),
              Address : "4450 Honeysuckle Lane, Seattle, WA, 98101",
              GovId : "S152-780-97-415-0",
              GovIdType : "Passport"
          }
      ];
      export const VEHICLE = [
          {
              VIN : "1N4AL11D75C109151",
              Type : "Sedan",
              Year : 2011,
              Make : "Audi",
              Model : "A5",
              Color : "Silver"
          },
          {
              VIN : "KM8SRDHF6EU074761",
              Type : "Sedan",
              Year : 2015,
              Make : "Tesla",
              Model : "Model S",
              Color : "Blue"
          },
          {
              VIN : "3HGGK5G53FM761765",
              Type : "Motorcycle",
              Year : 2011,
              Make : "Ducati",
              Model : "Monster 1200",
              Color : "Yellow"
          },
          {
              VIN : "1HVBBAANXWH544237",
              Type : "Semi",
              Year : 2009,
              Make : "Ford",
              Model : "F 150",
              Color : "Black"
          },
          {
              VIN : "1C4RJFAG0FC625797",
              Type : "Sedan",
              Year : 2019,
              Make : "Mercedes",
              Model : "CLK 350",
              Color : "White"
          }
      ];
      export const VEHICLE_REGISTRATION = [
          {
              VIN : "1N4AL11D75C109151",
              LicensePlateNumber : "LEWISR261LL",
              State : "WA",
              City : "Seattle",
              ValidFromDate : new Date("2017-08-21"),
              ValidToDate : new Date("2020-05-11"),
              PendingPenaltyTicketAmount : new Decimal(9025, -2),
              Owners : {
                  PrimaryOwner : { PersonId : "" },
                  SecondaryOwners : EMPTY_SECONDARY_OWNERS
              }
          },
          {
              VIN : "KM8SRDHF6EU074761",
              LicensePlateNumber : "CA762X",
              State : "WA",
              City : "Kent",
              PendingPenaltyTicketAmount : new Decimal(13075, -2),
              ValidFromDate : new Date("2017-09-14"),
              ValidToDate : new Date("2020-06-25"),
              Owners : {
                  PrimaryOwner : { PersonId : "" },
                  SecondaryOwners : EMPTY_SECONDARY_OWNERS
              }
          },
          {
              VIN : "3HGGK5G53FM761765",
              LicensePlateNumber : "CD820Z",
              State : "WA",
              City : "Everett",
              PendingPenaltyTicketAmount : new Decimal(44230, -2),
              ValidFromDate : new Date("2011-03-17"),
              ValidToDate : new Date("2021-03-24"),
              Owners : {
                  PrimaryOwner : { PersonId : "" },
                  SecondaryOwners : EMPTY_SECONDARY_OWNERS
              }
          },
          {
              VIN : "1HVBBAANXWH544237",
              LicensePlateNumber : "LS477D",
              State : "WA",
              City : "Tacoma",
              PendingPenaltyTicketAmount : new Decimal(4220, -2),
              ValidFromDate : new Date("2011-10-26"),
              ValidToDate : new Date("2023-09-25"),
              Owners : {
                  PrimaryOwner : { PersonId : "" },
                  SecondaryOwners : EMPTY_SECONDARY_OWNERS
              }
          },
          {
              VIN : "1C4RJFAG0FC625797",
              LicensePlateNumber : "TH393F",
              State : "WA",
              City : "Olympia",
              PendingPenaltyTicketAmount : new Decimal(3045, -2),
              ValidFromDate : new Date("2013-09-02"),
              ValidToDate : new Date("2024-03-19"),
              Owners : {
                  PrimaryOwner : { PersonId : "" },
                  SecondaryOwners : EMPTY_SECONDARY_OWNERS
              }
          }
      ];
      ```

   1. `Util.ts` - `ion-js` パッケージからインポートするユーティリティモジュールで、[Amazon Ion](ion.md) データを変換、解析、および出力するヘルパー関数を提供します。

      ```
      /*
       * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
       * SPDX-License-Identifier: MIT-0
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy of this
       * software and associated documentation files (the "Software"), to deal in the Software
       * without restriction, including without limitation the rights to use, copy, modify,
       * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
       * permit persons to whom the Software is furnished to do so.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
       * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
       * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
       * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
       * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
       * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
       */
      
      import { Result, TransactionExecutor } from "amazon-qldb-driver-nodejs";
      import { GetBlockResponse, GetDigestResponse, ValueHolder } from "aws-sdk/clients/qldb";
      import { 
          Decimal, 
          decodeUtf8,
          dom,
          IonTypes, 
          makePrettyWriter, 
          makeReader, 
          Reader, 
          Timestamp, 
          toBase64, 
          Writer
      } from "ion-js";
      
      import { error } from "./LogUtil";
      
      
      
      /**
       * TODO: Replace this with json.stringify
       * Returns the string representation of a given BlockResponse.
       * @param blockResponse The BlockResponse to convert to string.
       * @returns The string representation of the supplied BlockResponse.
       */
      export function blockResponseToString(blockResponse: GetBlockResponse): string {
          let stringBuilder: string = "";
          if (blockResponse.Block.IonText) {
              stringBuilder = stringBuilder + "Block: " + blockResponse.Block.IonText + ", ";
          }
          if (blockResponse.Proof.IonText) {
              stringBuilder = stringBuilder + "Proof: " + blockResponse.Proof.IonText;
          }
          stringBuilder = "{" + stringBuilder + "}";
          const writer: Writer = makePrettyWriter();
          const reader: Reader = makeReader(stringBuilder);
          writer.writeValues(reader);
          return decodeUtf8(writer.getBytes());
      }
      
      /**
       * TODO: Replace this with json.stringify
       * Returns the string representation of a given GetDigestResponse.
       * @param digestResponse The GetDigestResponse to convert to string.
       * @returns The string representation of the supplied GetDigestResponse.
       */
      export function digestResponseToString(digestResponse: GetDigestResponse): string {
          let stringBuilder: string = "";
          if (digestResponse.Digest) {
              stringBuilder += "Digest: " + JSON.stringify(toBase64(<Uint8Array> digestResponse.Digest)) + ", ";
          }
          if (digestResponse.DigestTipAddress.IonText) {
              stringBuilder += "DigestTipAddress: " + digestResponse.DigestTipAddress.IonText;
          }
          stringBuilder = "{" + stringBuilder + "}";
          const writer: Writer = makePrettyWriter();
          const reader: Reader = makeReader(stringBuilder);
          writer.writeValues(reader);
          return decodeUtf8(writer.getBytes());
      }
      
      /**
       * Get the document IDs from the given table.
       * @param txn The {@linkcode TransactionExecutor} for lambda execute.
       * @param tableName The table name to query.
       * @param field A field to query.
       * @param value The key of the given field.
       * @returns Promise which fulfills with the document ID as a string.
       */
      export async function getDocumentId(
          txn: TransactionExecutor,
          tableName: string,
          field: string,
          value: string
      ): Promise<string> {
          const query: string = `SELECT id FROM ${tableName} AS t BY id WHERE t.${field} = ?`;
          let documentId: string = undefined;
          await txn.execute(query, value).then((result: Result) => {
              const resultList: dom.Value[] = result.getResultList();
              if (resultList.length === 0) {
                  throw new Error(`Unable to retrieve document ID using ${value}.`);
              }
              documentId = resultList[0].get("id").stringValue();
      
          }).catch((err: any) => {
              error(`Error getting documentId: ${err}`);
          });
          return documentId;
      }
      
      /**
       * Sleep for the specified amount of time.
       * @param ms The amount of time to sleep in milliseconds.
       * @returns Promise which fulfills with void.
       */
      export function sleep(ms: number): Promise<void> {
          return new Promise(resolve => setTimeout(resolve, ms));
      }
      
      /**
       * Find the value of a given path in an Ion value. The path should contain a blob value.
       * @param value The Ion value that contains the journal block attributes.
       * @param path The path to a certain attribute.
       * @returns Uint8Array value of the blob, or null if the attribute cannot be found in the Ion value
       *                  or is not of type Blob
       */
      export function getBlobValue(value: dom.Value, path: string): Uint8Array | null {
          const attribute: dom.Value = value.get(path);
          if (attribute !== null && attribute.getType() === IonTypes.BLOB) {
              return attribute.uInt8ArrayValue();
          }
          return null;
      }
      
      /**
       * TODO: Replace this with json.stringify
       * Returns the string representation of a given ValueHolder.
       * @param valueHolder The ValueHolder to convert to string.
       * @returns The string representation of the supplied ValueHolder.
       */
      export function valueHolderToString(valueHolder: ValueHolder): string {
          const stringBuilder: string = `{ IonText: ${valueHolder.IonText}}`;
          const writer: Writer = makePrettyWriter();
          const reader: Reader = makeReader(stringBuilder);
          writer.writeValues(reader);
          return decodeUtf8(writer.getBytes());
      }
      
      /**
       * Converts a given value to Ion using the provided writer.
       * @param value The value to convert to Ion.
       * @param ionWriter The Writer to pass the value into.
       * @throws Error: If the given value cannot be converted to Ion.
       */
      export function writeValueAsIon(value: any, ionWriter: Writer): void {
          switch (typeof value) {
              case "string":
                  ionWriter.writeString(value);
                  break;
              case "boolean":
                  ionWriter.writeBoolean(value);
                  break;
              case "number":
                      ionWriter.writeInt(value);
                      break;
              case "object":
                  if (Array.isArray(value)) {
                      // Object is an array.
                      ionWriter.stepIn(IonTypes.LIST);
      
                      for (const element of value) {
                          writeValueAsIon(element, ionWriter);
                      }
      
                      ionWriter.stepOut();
                  } else if (value instanceof Date) {
                      // Object is a Date.
                      ionWriter.writeTimestamp(Timestamp.parse(value.toISOString()));
                  } else if (value instanceof Decimal) {
                      // Object is a Decimal.
                      ionWriter.writeDecimal(value);
                  } else if (value === null) {
                      ionWriter.writeNull(IonTypes.NULL);
                  } else {
                      // Object is a struct.
                      ionWriter.stepIn(IonTypes.STRUCT);
      
                      for (const key of Object.keys(value)) {
                          ionWriter.writeFieldName(key);
                          writeValueAsIon(value[key], ionWriter);
                      }
                      ionWriter.stepOut();
                  }
                  break;
              default:
                  throw new Error(`Cannot convert to Ion for type: ${(typeof value)}.`);
          }
      }
      ```
**注記**  
`getDocumentId` 関数は、システムによって割り当てられたドキュメント ID をテーブルから返すクエリを実行します。詳細については、「[BY 句を使用したドキュメント ID のクエリの実行](working.metadata.by-clause.md)」を参照してください。

1. 次のプログラム (`InsertDocument.ts`) を使用してサンプルデータをテーブルに挿入します。

   ```
   /*
    * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
    * SPDX-License-Identifier: MIT-0
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this
    * software and associated documentation files (the "Software"), to deal in the Software
    * without restriction, including without limitation the rights to use, copy, modify,
    * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    * permit persons to whom the Software is furnished to do so.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
   
   import { QldbDriver, Result, TransactionExecutor } from "amazon-qldb-driver-nodejs";
   import { dom } from "ion-js";
   
   import { getQldbDriver } from "./ConnectToLedger";
   import { DRIVERS_LICENSE, PERSON, VEHICLE, VEHICLE_REGISTRATION } from "./model/SampleData";
   import {
       DRIVERS_LICENSE_TABLE_NAME,
       PERSON_TABLE_NAME,
       VEHICLE_REGISTRATION_TABLE_NAME,
       VEHICLE_TABLE_NAME
   } from "./qldb/Constants";
   import { error, log } from "./qldb/LogUtil";
   
   /**
    * Insert the given list of documents into a table in a single transaction.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @param tableName Name of the table to insert documents into.
    * @param documents List of documents to insert.
    * @returns Promise which fulfills with a {@linkcode Result} object.
    */
   export async function insertDocument(
       txn: TransactionExecutor,
       tableName: string,
       documents: object[]
   ): Promise<Result> {
       const statement: string = `INSERT INTO ${tableName} ?`;
       const result: Result = await txn.execute(statement, documents);
       return result;
   }
   
   /**
    * Handle the insertion of documents and updating PersonIds all in a single transaction.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @returns Promise which fulfills with void.
    */
   async function updateAndInsertDocuments(txn: TransactionExecutor): Promise<void> {
       log("Inserting multiple documents into the 'Person' table...");
       const documentIds: Result = await insertDocument(txn, PERSON_TABLE_NAME, PERSON);
   
       const listOfDocumentIds: dom.Value[] = documentIds.getResultList();
       log("Updating PersonIds for 'DriversLicense' and PrimaryOwner for 'VehicleRegistration'...");
       updatePersonId(listOfDocumentIds);
   
       log("Inserting multiple documents into the remaining tables...");
       await Promise.all([
           insertDocument(txn, DRIVERS_LICENSE_TABLE_NAME, DRIVERS_LICENSE),
           insertDocument(txn, VEHICLE_REGISTRATION_TABLE_NAME, VEHICLE_REGISTRATION),
           insertDocument(txn, VEHICLE_TABLE_NAME, VEHICLE)
       ]);
   }
   
   /**
    * Update the PersonId value for DriversLicense records and the PrimaryOwner value for VehicleRegistration records.
    * @param documentIds List of document IDs.
    */
   export function updatePersonId(documentIds: dom.Value[]): void {
       documentIds.forEach((value: dom.Value, i: number) => {
           const documentId: string = value.get("documentId").stringValue();
           DRIVERS_LICENSE[i].PersonId = documentId;
           VEHICLE_REGISTRATION[i].Owners.PrimaryOwner.PersonId = documentId;
       });
   }
   
   /**
    * Insert documents into a table in a QLDB ledger.
    * @returns Promise which fulfills with void.
    */
   const main = async function(): Promise<void> {
       try {
           const qldbDriver: QldbDriver = getQldbDriver();
           await qldbDriver.executeLambda(async (txn: TransactionExecutor) => {
               await updateAndInsertDocuments(txn);
           });
       } catch (e) {
           error(`Unable to insert documents: ${e}`);
       }
   }
   
   if (require.main === module) {
       main();
   }
   ```
**注記**  
このプログラムは、パラメータ化された値を渡して `execute` 関数を呼び出す方法を示しています。実行する PartiQL ステートメントに加えて、データパラメータを渡すことができます。ステートメント文字列の変数プレースホルダーとして疑問符 (`?`) を使用します。
`INSERT` ステートメントが成功すると、 挿入された各ドキュメントの `id` が返されます。

1. トランスパイルされたプログラムを実行するには、次のコマンドを入力します。

   ```
   node dist/InsertDocument.js
   ```

次に、`SELECT` ステートメントを使用すると、`vehicle-registration` 台帳のテーブルからデータを読み取ることができます。[ステップ 4: 台帳のテーブルにクエリを実行する](getting-started.nodejs.step-4.md) に進みます。

# ステップ 4: 台帳のテーブルにクエリを実行する
<a name="getting-started.nodejs.step-4"></a>

**重要**  
サポート終了通知: 既存のお客様は、07/31/2025 のサポート終了まで Amazon QLDB を使用できます。詳細については、[「Amazon QLDB 台帳を Amazon Aurora PostgreSQL に移行する](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)」を参照してください。

Amazon QLDB 台帳にテーブルを作成し、データをロードした後は、クエリを実行して、挿入した車両登録データを確認できます。QLDB は [PartiQL](ql-reference.md) をクエリ言語として使用し、[Amazon Ion](ion.md) をドキュメント指向のデータモデルとして使用します。

PartiQL は、Ion で動作するように拡張されたオープンソースの SQL 互換のクエリ言語です。PartiQL を使用すると、使い慣れた SQL 演算子を使用してデータを挿入、クエリ、および管理できます。Amazon Ion は JSON のスーパーセットです。Ion はオープンソースのドキュメントベースのデータ形式であり、構造化データ、半構造化データ、およびネストされたデータを柔軟に保存および処理できます。

このステップでは、`SELECT` ステートメントを使用して、`vehicle-registration` 台帳のテーブルからデータを読み取ります。

**警告**  
インデックス付きルックアップなしで QLDB でクエリを実行すると、完全なテーブルスキャンが呼び出されます。PartiQL は SQL 互換であるため、このようなクエリをサポートしています。ただし、QLDB の本番環境のユースケースではテーブルスキャンを実行しないでください。テーブルスキャンより、同時実行の競合やトランザクションのタイムアウトなど、大きなテーブルでパフォーマンスの問題が発生する可能性があります。  
テーブルスキャンを回避するには、インデックス付きフィールドまたはドキュメント ID で**等価演算子を使用する `WHERE` 述語句でステートメントを実行する必要があります (例: `WHERE indexedField = 123` または `WHERE indexedField IN (456, 789)`)。詳細については、「[クエリパフォーマンスの最適化](working.optimize.md)」を参照してください。

**テーブルのクエリを実行するには**

1. 次のプログラム (`FindVehicles.ts`) を使用して台帳の人物に登録されているすべての車両をクエリします。

   ```
   /*
    * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
    * SPDX-License-Identifier: MIT-0
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this
    * software and associated documentation files (the "Software"), to deal in the Software
    * without restriction, including without limitation the rights to use, copy, modify,
    * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    * permit persons to whom the Software is furnished to do so.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
   
   import { QldbDriver, Result, TransactionExecutor } from "amazon-qldb-driver-nodejs";
   import { dom } from "ion-js";
   
   import { getQldbDriver } from "./ConnectToLedger";
   import { PERSON } from "./model/SampleData";
   import { PERSON_TABLE_NAME } from "./qldb/Constants";
   import { error, log } from "./qldb/LogUtil";
   import { getDocumentId } from "./qldb/Util";
   import { prettyPrintResultList } from "./ScanTable";
   
   /**
    * Query 'Vehicle' and 'VehicleRegistration' tables using a unique document ID in one transaction.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @param govId The owner's government ID.
    * @returns Promise which fulfills with void.
    */
   async function findVehiclesForOwner(txn: TransactionExecutor, govId: string): Promise<void> {
       const documentId: string = await getDocumentId(txn, PERSON_TABLE_NAME, "GovId", govId);
       const query: string = "SELECT Vehicle FROM Vehicle INNER JOIN VehicleRegistration AS r " +
                           "ON Vehicle.VIN = r.VIN WHERE r.Owners.PrimaryOwner.PersonId = ?";
   
       await txn.execute(query, documentId).then((result: Result) => {
           const resultList: dom.Value[] = result.getResultList();
           log(`List of vehicles for owner with GovId: ${govId}`);
           prettyPrintResultList(resultList);
       });
   }
   
   /**
    * Find all vehicles registered under a person.
    * @returns Promise which fulfills with void.
    */
   const main = async function(): Promise<void> {
       try {
           const qldbDriver: QldbDriver = getQldbDriver();
           await qldbDriver.executeLambda(async (txn: TransactionExecutor) => {
               await findVehiclesForOwner(txn, PERSON[0].GovId);
           });
       } catch (e) {
           error(`Error getting vehicles for owner: ${e}`);
       }
   }
   
   if (require.main === module) {
       main();
   }
   ```
**注記**  
まず、このプログラムでは、`GovId LEWISR261LL` を使用してドキュメントの `Person` テーブルに対してクエリを実行し、`id` メタデータフィールドを取得します。  
次に、このドキュメント `id` を外部キーとして使用して、`PrimaryOwner.PersonId` によって `VehicleRegistration` テーブルをクエリします。また、`VehicleRegistration` が `VIN` フィールドの `Vehicle` テーブルと結合されます。

1. トランスパイルされたプログラムを実行するには、次のコマンドを入力します。

   ```
   node dist/FindVehicles.js
   ```

`vehicle-registration` 台帳のテーブルのドキュメントの変更方法については、「[ステップ 5: 台帳内のドキュメントを変更する](getting-started.nodejs.step-5.md)」を参照してください。

# ステップ 5: 台帳内のドキュメントを変更する
<a name="getting-started.nodejs.step-5"></a>

**重要**  
サポート終了通知: 既存のお客様は、07/31/2025 のサポート終了まで Amazon QLDB を使用できます。詳細については、[「Amazon QLDB 台帳を Amazon Aurora PostgreSQL に移行する](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)」を参照してください。

操作するデータを用意できたところで、Amazon QLDB で `vehicle-registration` 台帳のドキュメントに変更を加えることができます。このステップでは、次のコード例により、データ操作言語 (DML) ステートメントを実行する方法を示します。これらのステートメントは、ある車両の主所有者を更新し、別の車両に第二所有者を追加します。

**ドキュメントに変更を加えるには**

1. 次のプログラム (`TransferVehicleOwnership.ts`) を使用して、台帳で VIN が `1N4AL11D75C109151` の車両の主所有者を更新します。

   ```
   /*
    * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
    * SPDX-License-Identifier: MIT-0
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this
    * software and associated documentation files (the "Software"), to deal in the Software
    * without restriction, including without limitation the rights to use, copy, modify,
    * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    * permit persons to whom the Software is furnished to do so.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
   
   import { QldbDriver, Result, TransactionExecutor } from "amazon-qldb-driver-nodejs";
   import { dom } from "ion-js";
   
   import { getQldbDriver } from "./ConnectToLedger";
   import { PERSON, VEHICLE } from "./model/SampleData";
   import { PERSON_TABLE_NAME } from "./qldb/Constants";
   import { error, log } from "./qldb/LogUtil";
   import { getDocumentId } from "./qldb/Util";
   
   /**
    * Query a driver's information using the given ID.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @param documentId The unique ID of a document in the Person table.
    * @returns Promise which fulfills with an Ion value containing the person.
    */
   export async function findPersonFromDocumentId(txn: TransactionExecutor, documentId: string): Promise<dom.Value> {
       const query: string = "SELECT p.* FROM Person AS p BY pid WHERE pid = ?";
   
       let personId: dom.Value;
       await txn.execute(query, documentId).then((result: Result) => {
           const resultList: dom.Value[] = result.getResultList();
           if (resultList.length === 0) {
               throw new Error(`Unable to find person with ID: ${documentId}.`);
           }
           personId = resultList[0];
       });
       return personId;
   }
   
   /**
    * Find the primary owner for the given VIN.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @param vin The VIN to find primary owner for.
    * @returns Promise which fulfills with an Ion value containing the primary owner.
    */
   export async function findPrimaryOwnerForVehicle(txn: TransactionExecutor, vin: string): Promise<dom.Value> {
       log(`Finding primary owner for vehicle with VIN: ${vin}`);
       const query: string = "SELECT Owners.PrimaryOwner.PersonId FROM VehicleRegistration AS v WHERE v.VIN = ?";
   
       let documentId: string = undefined;
       await txn.execute(query, vin).then((result: Result) => {
           const resultList: dom.Value[] = result.getResultList();
           if (resultList.length === 0) {
               throw new Error(`Unable to retrieve document ID using ${vin}.`);
           }
           const PersonIdValue: dom.Value = resultList[0].get("PersonId");
           if (PersonIdValue === null) {
               throw new Error(`Expected field name PersonId not found.`);
           }
           documentId = PersonIdValue.stringValue();
       });
       return findPersonFromDocumentId(txn, documentId);
   }
   
   /**
    * Update the primary owner for a vehicle using the given VIN.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @param vin The VIN for the vehicle to operate on.
    * @param documentId New PersonId for the primary owner.
    * @returns Promise which fulfills with void.
    */
   async function updateVehicleRegistration(txn: TransactionExecutor, vin: string, documentId: string): Promise<void> {
       const statement: string = "UPDATE VehicleRegistration AS r SET r.Owners.PrimaryOwner.PersonId = ? WHERE r.VIN = ?";
   
       log(`Updating the primary owner for vehicle with VIN: ${vin}...`);
       await txn.execute(statement, documentId, vin).then((result: Result) => {
           const resultList: dom.Value[] = result.getResultList();
           if (resultList.length === 0) {
               throw new Error("Unable to transfer vehicle, could not find registration.");
           }
           log(`Successfully transferred vehicle with VIN ${vin} to new owner.`);
       });
   }
   
   /**
    * Validate the current owner of the given vehicle and transfer its ownership to a new owner in a single transaction.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @param vin The VIN of the vehicle to transfer ownership of.
    * @param currentOwner The GovId of the current owner of the vehicle.
    * @param newOwner The GovId of the new owner of the vehicle.
    */
   export async function validateAndUpdateRegistration(
       txn: TransactionExecutor,
       vin: string,
       currentOwner: string,
       newOwner: string
   ): Promise<void> {
       const primaryOwner: dom.Value = await findPrimaryOwnerForVehicle(txn, vin);
       const govIdValue: dom.Value = primaryOwner.get("GovId");
       if (govIdValue !== null && govIdValue.stringValue() !== currentOwner) {
           log("Incorrect primary owner identified for vehicle, unable to transfer.");
       }
       else {
           const documentId: string = await getDocumentId(txn, PERSON_TABLE_NAME, "GovId", newOwner);
           await updateVehicleRegistration(txn, vin, documentId);
           log("Successfully transferred vehicle ownership!");
       }
   }
   
   /**
    * Find primary owner for a particular vehicle's VIN.
    * Transfer to another primary owner for a particular vehicle's VIN.
    * @returns Promise which fulfills with void.
    */
   const main = async function(): Promise<void> {
       try {
           const qldbDriver: QldbDriver = getQldbDriver();
   
           const vin: string = VEHICLE[0].VIN;
           const previousOwnerGovId: string = PERSON[0].GovId;
           const newPrimaryOwnerGovId: string = PERSON[1].GovId;
   
           await qldbDriver.executeLambda(async (txn: TransactionExecutor) => {
               await validateAndUpdateRegistration(txn, vin, previousOwnerGovId,  newPrimaryOwnerGovId);
           });
       } catch (e) {
           error(`Unable to connect and run queries: ${e}`);
       }
   }
   
   if (require.main === module) {
       main();
   }
   ```

1. トランスパイルされたプログラムを実行するには、次のコマンドを入力します。

   ```
   node dist/TransferVehicleOwnership.js
   ```

1. 次のプログラム (`AddSecondaryOwner.ts`) を使用して、台帳で VIN が `KM8SRDHF6EU074761` の車両に第二所有者を追加します。

   ```
   /*
    * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
    * SPDX-License-Identifier: MIT-0
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this
    * software and associated documentation files (the "Software"), to deal in the Software
    * without restriction, including without limitation the rights to use, copy, modify,
    * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    * permit persons to whom the Software is furnished to do so.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
   
   import { QldbDriver, Result, TransactionExecutor } from "amazon-qldb-driver-nodejs";
   import { dom } from "ion-js";
   
   import { getQldbDriver } from "./ConnectToLedger";
   import { PERSON, VEHICLE_REGISTRATION } from "./model/SampleData";
   import { PERSON_TABLE_NAME } from "./qldb/Constants";
   import { error, log } from "./qldb/LogUtil";
   import { getDocumentId } from "./qldb/Util";
   import { prettyPrintResultList } from "./ScanTable";
   
   /**
    * Add a secondary owner into 'VehicleRegistration' table for a particular VIN.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @param vin VIN of the vehicle to query.
    * @param secondaryOwnerId The secondary owner's person ID.
    * @returns Promise which fulfills with void.
    */
   export async function addSecondaryOwner(
       txn: TransactionExecutor, 
       vin: string, 
       secondaryOwnerId: string
   ): Promise<void> {
       log(`Inserting secondary owner for vehicle with VIN: ${vin}`);
       const query: string =
           `FROM VehicleRegistration AS v WHERE v.VIN = ? INSERT INTO v.Owners.SecondaryOwners VALUE ?`;
   
       const personToInsert = {PersonId: secondaryOwnerId};
       await txn.execute(query, vin, personToInsert).then(async (result: Result) => {
           const resultList: dom.Value[] = result.getResultList();
           log("VehicleRegistration Document IDs which had secondary owners added: ");
           prettyPrintResultList(resultList);
       });
   }
   
   /**
    * Query for a document ID with a government ID.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @param governmentId The government ID to query with.
    * @returns Promise which fulfills with the document ID as a string.
    */
   export async function getDocumentIdByGovId(txn: TransactionExecutor, governmentId: string): Promise<string> {
       const documentId: string = await getDocumentId(txn, PERSON_TABLE_NAME, "GovId", governmentId);
       return documentId;
   }
   
   /**
    * Check whether a driver has already been registered for the given VIN.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @param vin VIN of the vehicle to query.
    * @param secondaryOwnerId The secondary owner's person ID.
    * @returns Promise which fulfills with a boolean.
    */
   export async function isSecondaryOwnerForVehicle(
       txn: TransactionExecutor,
       vin: string,
       secondaryOwnerId: string
   ): Promise<boolean> {
       log(`Finding secondary owners for vehicle with VIN: ${vin}`);
       const query: string = "SELECT Owners.SecondaryOwners FROM VehicleRegistration AS v WHERE v.VIN = ?";
   
       let doesExist: boolean = false;
   
       await txn.execute(query, vin).then((result: Result) => {
           const resultList: dom.Value[] = result.getResultList();
   
           resultList.forEach((value: dom.Value) => {
               const secondaryOwnersList: dom.Value[] = value.get("SecondaryOwners").elements();
   
               secondaryOwnersList.forEach((secondaryOwner) => {
                   const personId: dom.Value = secondaryOwner.get("PersonId");
                   if (personId !== null &&  personId.stringValue() === secondaryOwnerId) {
                       doesExist = true;
                   }
               });
           });
       });
       return doesExist;
   }
   
   /**
    * Finds and adds secondary owners for a vehicle.
    * @returns Promise which fulfills with void.
    */
   const main = async function(): Promise<void> {
       try {
           const qldbDriver: QldbDriver = getQldbDriver();
           const vin: string = VEHICLE_REGISTRATION[1].VIN;
           const govId: string = PERSON[0].GovId;
   
           await qldbDriver.executeLambda(async (txn: TransactionExecutor) => {
               const documentId: string = await getDocumentIdByGovId(txn, govId);
   
               if (await isSecondaryOwnerForVehicle(txn, vin, documentId)) {
                   log(`Person with ID ${documentId} has already been added as a secondary owner of this vehicle.`);
               } else {
                   await addSecondaryOwner(txn, vin, documentId);
               }
           });
   
           log("Secondary owners successfully updated.");
       } catch (e) {
           error(`Unable to add secondary owner: ${e}`);
       } 
   }
   
   if (require.main === module) {
       main();
   }
   ```

1. トランスパイルされたプログラムを実行するには、次のコマンドを入力します。

   ```
   node dist/AddSecondaryOwner.js
   ```

これらの変更を `vehicle-registration` 台帳で確認するには、「[ステップ 6: ドキュメントのリビジョン履歴を表示する](getting-started.nodejs.step-6.md)」を参照してください。

# ステップ 6: ドキュメントのリビジョン履歴を表示する
<a name="getting-started.nodejs.step-6"></a>

**重要**  
サポート終了通知: 既存のお客様は、07/31/2025 のサポート終了まで Amazon QLDB を使用できます。詳細については、[「Amazon QLDB 台帳を Amazon Aurora PostgreSQL に移行する](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)」を参照してください。

[前のステップ](getting-started.nodejs.step-5.md)で車両の登録データを変更すると、登録されているすべての所有者とその他の更新されたフィールドの履歴に対してクエリを実行できます。このステップでは、`vehicle-registration` 台帳の `VehicleRegistration` テーブルに含まれているドキュメントのリビジョン履歴のクエリを実行します。

**リビジョン履歴を表示するには**

1. 次のプログラム (`QueryHistory.ts`) を使用して、VIN が `1N4AL11D75C109151` の `VehicleRegistration` ドキュメントのリビジョン履歴をクエリします。

   ```
   /*
    * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
    * SPDX-License-Identifier: MIT-0
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this
    * software and associated documentation files (the "Software"), to deal in the Software
    * without restriction, including without limitation the rights to use, copy, modify,
    * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    * permit persons to whom the Software is furnished to do so.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
   
   import { QldbDriver, Result, TransactionExecutor } from "amazon-qldb-driver-nodejs";
   import { dom } from "ion-js";
   
   import { getQldbDriver } from "./ConnectToLedger";
   import { VEHICLE_REGISTRATION } from "./model/SampleData";
   import { VEHICLE_REGISTRATION_TABLE_NAME } from "./qldb/Constants";
   import { prettyPrintResultList } from "./ScanTable";
   import { error, log } from "./qldb/LogUtil";
   import { getDocumentId } from "./qldb/Util";
   
   /**
    * Find previous primary owners for the given VIN in a single transaction.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @param vin The VIN to find previous primary owners for.
    * @returns Promise which fulfills with void.
    */
   async function previousPrimaryOwners(txn: TransactionExecutor, vin: string): Promise<void> {
       const documentId: string = await getDocumentId(txn, VEHICLE_REGISTRATION_TABLE_NAME, "VIN", vin);
       const todaysDate: Date = new Date();
       // set todaysDate back one minute to ensure end time is in the past
       // by the time the request reaches our backend
       todaysDate.setMinutes(todaysDate.getMinutes() - 1);
       const threeMonthsAgo: Date = new Date(todaysDate);
       threeMonthsAgo.setMonth(todaysDate.getMonth() - 3);
   
       const query: string =
           `SELECT data.Owners.PrimaryOwner, metadata.version FROM history ` +
           `(${VEHICLE_REGISTRATION_TABLE_NAME}, \`${threeMonthsAgo.toISOString()}\`, \`${todaysDate.toISOString()}\`) ` +
           `AS h WHERE h.metadata.id = ?`;
   
       await txn.execute(query, documentId).then((result: Result) => {
           log(`Querying the 'VehicleRegistration' table's history using VIN: ${vin}.`);
           const resultList: dom.Value[] = result.getResultList();
           prettyPrintResultList(resultList);
       });
   }
   
   /**
    * Query a table's history for a particular set of documents.
    * @returns Promise which fulfills with void.
    */
   const main = async function(): Promise<void> {
       try {
           const qldbDriver: QldbDriver = getQldbDriver();
           const vin: string = VEHICLE_REGISTRATION[0].VIN;
           await qldbDriver.executeLambda(async (txn: TransactionExecutor) => {
               await previousPrimaryOwners(txn, vin);
           });
       } catch (e) {
           error(`Unable to query history to find previous owners: ${e}`);
       }
   }
   
   if (require.main === module) {
       main();
   }
   ```
**注記**  
以下の構文で組み込みの「[履歴関数](working.history.md#working.history.function)」のクエリを実行することで、ドキュメントのリビジョン履歴を表示できます。  

     ```
     SELECT * FROM history( table_name [, `start-time` [, `end-time` ] ] ) AS h
     [ WHERE h.metadata.id = 'id' ]
     ```
*start-time* および *end-time* はいずれもオプションです。これらは、バックティック (``...``) で示すことができる Amazon Ion リテラル値です。詳細については、「[Amazon QLDB での PartiQL による Ion のクエリ](ql-reference.query.md)」を参照してください。
ベストプラクティスとして、履歴クエリは日付範囲 (*start-time* および *end-time*) とドキュメント ID (`metadata.id`) の両方で修飾します。QLDB はトランザクション内の `SELECT` クエリを処理します。これらは、[トランザクションタイムアウト制限](limits.md#limits.fixed)が適用されます。  
QLDB 履歴はドキュメント ID によってインデックス付けされるため、現時点では追加の履歴インデックスを作成することはできません。開始時刻と終了時刻を含む履歴クエリでは、日付範囲修飾のメリットが得られます。

1. トランスパイルされたプログラムを実行するには、次のコマンドを入力します。

   ```
   node dist/QueryHistory.js
   ```

`vehicle-registration` 台帳のドキュメントリビジョンを暗号的に検証するには、「[ステップ 7: 台帳内のドキュメントを検証する](getting-started.nodejs.step-7.md)」に進みます。

# ステップ 7: 台帳内のドキュメントを検証する
<a name="getting-started.nodejs.step-7"></a>

**重要**  
サポート終了通知: 既存のお客様は、07/31/2025 のサポート終了まで Amazon QLDB を使用できます。詳細については、[「Amazon QLDB 台帳を Amazon Aurora PostgreSQL に移行する](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)」を参照してください。

Amazon QLDB では、SHA-256 の暗号的ハッシュを使用して、台帳のジャーナルのドキュメントの整合性を効率的に検証できます。検証と暗号的ハッシュが QLDB でどのように機能するかについては、「[Amazon QLDB でのデータ検証](verification.md)」を参照してください。

このステップでは、`vehicle-registration` 台帳の `VehicleRegistration` テーブルのドキュメントリビジョンを確認します。まず、ダイジェストをリクエストします。ダイジェストは出力ファイルとして返され、台帳の変更履歴全体の署名として機能します。次に、そのダイジェストに関連するリビジョンの証明をリクエストします。この証明を使用して、すべての検証チェックに合格すると、リビジョンの整合性が検証されます。

**ドキュメントのリビジョンを検証するには**

1. 以下の `.ts` ファイルを確認します。これらのファイルには、検証に必要な QLDB オブジェクトが含まれています。

   1. `BlockAddress.ts`

      ```
      /*
       * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
       * SPDX-License-Identifier: MIT-0
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy of this
       * software and associated documentation files (the "Software"), to deal in the Software
       * without restriction, including without limitation the rights to use, copy, modify,
       * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
       * permit persons to whom the Software is furnished to do so.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
       * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
       * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
       * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
       * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
       * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
       */
      
      import { ValueHolder } from "aws-sdk/clients/qldb";
      import { dom, IonTypes } from "ion-js";
      
      export class BlockAddress {
          _strandId: string;
          _sequenceNo: number;
      
          constructor(strandId: string, sequenceNo: number) {
              this._strandId = strandId;
              this._sequenceNo = sequenceNo;
          }
      }
      
      /**
       * Convert a block address from an Ion value into a ValueHolder.
       * Shape of the ValueHolder must be: {'IonText': "{strandId: <"strandId">, sequenceNo: <sequenceNo>}"}
       * @param value The Ion value that contains the block address values to convert.
       * @returns The ValueHolder that contains the strandId and sequenceNo.
       */
      export function blockAddressToValueHolder(value: dom.Value): ValueHolder {
          const blockAddressValue : dom.Value = getBlockAddressValue(value);
          const strandId: string = getStrandId(blockAddressValue);
          const sequenceNo: number = getSequenceNo(blockAddressValue);
          const valueHolder: string = `{strandId: "${strandId}", sequenceNo: ${sequenceNo}}`;
          const blockAddress: ValueHolder = {IonText: valueHolder};
          return blockAddress;
      }
      
      /**
       * Helper method that to get the Metadata ID.
       * @param value The Ion value.
       * @returns The Metadata ID.
       */
      export function getMetadataId(value: dom.Value): string {
          const metaDataId: dom.Value = value.get("id");
          if (metaDataId === null) {
              throw new Error(`Expected field name id, but not found.`);
          }
          return metaDataId.stringValue();
      }
      
      /**
       * Helper method to get the Sequence No.
       * @param value The Ion value.
       * @returns The Sequence No.
       */
      export function getSequenceNo(value : dom.Value): number {
          const sequenceNo: dom.Value = value.get("sequenceNo");
          if (sequenceNo === null) {
              throw new Error(`Expected field name sequenceNo, but not found.`);
          }
          return sequenceNo.numberValue();
      }
      
      /**
       * Helper method to get the Strand ID.
       * @param value The Ion value.
       * @returns The Strand ID.
       */
      export function getStrandId(value: dom.Value): string {
          const strandId: dom.Value = value.get("strandId");
          if (strandId === null) {
              throw new Error(`Expected field name strandId, but not found.`);
          }
          return strandId.stringValue();
      }
      
      export function getBlockAddressValue(value: dom.Value) : dom.Value {
          const type = value.getType();
          if (type !== IonTypes.STRUCT) {
              throw new Error(`Unexpected format: expected struct, but got IonType: ${type.name}`);
          }
          const blockAddress: dom.Value = value.get("blockAddress");
          if (blockAddress == null) {
              throw new Error(`Expected field name blockAddress, but not found.`);
          }
          return blockAddress;
      }
      ```

   1. `Verifier.ts`

      ```
      /*
       * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
       * SPDX-License-Identifier: MIT-0
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy of this
       * software and associated documentation files (the "Software"), to deal in the Software
       * without restriction, including without limitation the rights to use, copy, modify,
       * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
       * permit persons to whom the Software is furnished to do so.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
       * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
       * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
       * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
       * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
       * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
       */
      
      import { Digest, ValueHolder } from "aws-sdk/clients/qldb";
      import { createHash } from "crypto";
      import { dom, toBase64 } from "ion-js";
      
      import { getBlobValue } from "./Util";
      
      const HASH_LENGTH: number = 32;
      const UPPER_BOUND: number = 8;
      
      /**
       * Build the candidate digest representing the entire ledger from the Proof hashes.
       * @param proof The Proof object.
       * @param leafHash The revision hash to pair with the first hash in the Proof hashes list.
       * @returns The calculated root hash.
       */
      function buildCandidateDigest(proof: ValueHolder, leafHash: Uint8Array): Uint8Array {
          const parsedProof: Uint8Array[] = parseProof(proof);
          const rootHash: Uint8Array = calculateRootHashFromInternalHash(parsedProof, leafHash);
          return rootHash;
      }
      
      /**
       * Combine the internal hashes and the leaf hash until only one root hash remains.
       * @param internalHashes An array of hash values.
       * @param leafHash The revision hash to pair with the first hash in the Proof hashes list.
       * @returns The root hash constructed by combining internal hashes.
       */
      function calculateRootHashFromInternalHash(internalHashes: Uint8Array[], leafHash: Uint8Array): Uint8Array {
          const rootHash: Uint8Array = internalHashes.reduce(joinHashesPairwise, leafHash);
          return rootHash;
      }
      
      /**
       * Compare two hash values by converting each Uint8Array byte, which is unsigned by default,
       * into a signed byte, assuming they are little endian.
       * @param hash1 The hash value to compare.
       * @param hash2 The hash value to compare.
       * @returns Zero if the hash values are equal, otherwise return the difference of the first pair of non-matching bytes.
       */
      function compareHashValues(hash1: Uint8Array, hash2: Uint8Array): number {
          if (hash1.length !== HASH_LENGTH || hash2.length !== HASH_LENGTH) {
              throw new Error("Invalid hash.");
          }
          for (let i = hash1.length-1; i >= 0; i--) {
              const difference: number = (hash1[i]<<24 >>24) - (hash2[i]<<24 >>24);
              if (difference !== 0) {
                  return difference;
              }
          }
          return 0;
      }
      
      /**
       * Helper method that concatenates two Uint8Array.
       * @param arrays List of array to concatenate, in the order provided.
       * @returns The concatenated array.
       */
      function concatenate(...arrays: Uint8Array[]): Uint8Array {
          let totalLength = 0;
          for (const arr of arrays) {
              totalLength += arr.length;
          }
          const result = new Uint8Array(totalLength);
          let offset = 0;
          for (const arr of arrays) {
              result.set(arr, offset);
              offset += arr.length;
          }
          return result;
      }
      
      /**
       * Flip a single random bit in the given hash value.
       * This method is intended to be used for purpose of demonstrating the QLDB verification features only.
       * @param original The hash value to alter.
       * @returns The altered hash with a single random bit changed.
       */
      export function flipRandomBit(original: any): Uint8Array {
          if (original.length === 0) {
              throw new Error("Array cannot be empty!");
          }
          const bytePos: number = Math.floor(Math.random() * original.length);
          const bitShift: number = Math.floor(Math.random() * UPPER_BOUND);
          const alteredHash: Uint8Array = original;
      
          alteredHash[bytePos] = alteredHash[bytePos] ^ (1 << bitShift);
          return alteredHash;
      }
      
      /**
       * Take two hash values, sort them, concatenate them, and generate a new hash value from the concatenated values.
       * @param h1 Byte array containing one of the hashes to compare.
       * @param h2 Byte array containing one of the hashes to compare.
       * @returns The concatenated array of hashes.
       */
      export function joinHashesPairwise(h1: Uint8Array, h2: Uint8Array): Uint8Array {
          if (h1.length === 0) {
              return h2;
          }
          if (h2.length === 0) {
              return h1;
          }
          let concat: Uint8Array;
          if (compareHashValues(h1, h2) < 0) {
              concat = concatenate(h1, h2);
          } else {
              concat = concatenate(h2, h1);
          }
          const hash = createHash('sha256');
          hash.update(concat);
          const newDigest: Uint8Array = hash.digest();
          return newDigest;
      }
      
      /**
       * Parse the Block object returned by QLDB and retrieve block hash.
       * @param valueHolder A structure containing an Ion string value.
       * @returns The block hash.
       */
      export function parseBlock(valueHolder: ValueHolder): Uint8Array {
          const block: dom.Value = dom.load(valueHolder.IonText);
          const blockHash: Uint8Array = getBlobValue(block, "blockHash");
          return blockHash;
      }
      
      /**
       * Parse the Proof object returned by QLDB into an iterator.
       * The Proof object returned by QLDB is a dictionary like the following:
       * {'IonText': '[{{<hash>}},{{<hash>}}]'}
       * @param valueHolder A structure containing an Ion string value.
       * @returns A list of hash values.
       */
      function parseProof(valueHolder: ValueHolder): Uint8Array[] {
          const proofs : dom.Value = dom.load(valueHolder.IonText);
          return proofs.elements().map(proof => proof.uInt8ArrayValue());
      }
      
      /**
       *  Verify document revision against the provided digest.
       * @param documentHash The SHA-256 value representing the document revision to be verified.
       * @param digest The SHA-256 hash value representing the ledger digest.
       * @param proof The Proof object retrieved from GetRevision.getRevision.
       * @returns If the document revision verifies against the ledger digest.
       */
      export function verifyDocument(documentHash: Uint8Array, digest: Digest, proof: ValueHolder): boolean {
          const candidateDigest = buildCandidateDigest(proof, documentHash);
          return (toBase64(<Uint8Array> digest) === toBase64(candidateDigest));
      }
      ```

1. 2 つの `.ts` プログラム (`GetDigest.ts` および `GetRevision.ts`) を使用して、次の手順を実行します。
   + `vehicle-registration` 台帳に新しいダイジェストをリクエストします。
   + `VehicleRegistration` テーブルの VIN が `1N4AL11D75C109151` のドキュメントについて、各リビジョンの証明をリクエストします。
   + 返されたダイジェストと証明を使用して、ダイジェストを再計算することで、リビジョンを検証します。

   `GetDigest.ts` プログラムには、次のコードが含まれています。

   ```
   /*
    * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
    * SPDX-License-Identifier: MIT-0
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this
    * software and associated documentation files (the "Software"), to deal in the Software
    * without restriction, including without limitation the rights to use, copy, modify,
    * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    * permit persons to whom the Software is furnished to do so.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
   
   import { QLDB } from "aws-sdk";
   import { GetDigestRequest, GetDigestResponse } from "aws-sdk/clients/qldb";
   
   import { LEDGER_NAME } from "./qldb/Constants";
   import { error, log } from "./qldb/LogUtil";
   import { digestResponseToString } from "./qldb/Util";
   
   /**
    * Get the digest of a ledger's journal.
    * @param ledgerName Name of the ledger to operate on.
    * @param qldbClient The QLDB control plane client to use.
    * @returns Promise which fulfills with a GetDigestResponse.
    */
   export async function getDigestResult(ledgerName: string, qldbClient: QLDB): Promise<GetDigestResponse> {
       const request: GetDigestRequest = {
           Name: ledgerName
       };
       const result: GetDigestResponse = await qldbClient.getDigest(request).promise();
       return result;
   }
   
   /**
    * This is an example for retrieving the digest of a particular ledger.
    * @returns Promise which fulfills with void.
    */
   const main = async function(): Promise<void> {
       try {
           const qldbClient: QLDB = new QLDB();
           log(`Retrieving the current digest for ledger: ${LEDGER_NAME}.`);
           const digest: GetDigestResponse = await getDigestResult(LEDGER_NAME, qldbClient);
           log(`Success. Ledger digest: \n${digestResponseToString(digest)}.`);
       } catch (e) {
           error(`Unable to get a ledger digest: ${e}`);
       }
   }
   
   if (require.main === module) {
       main();
   }
   ```
**注記**  
`getDigest` 関数を使用して、台帳のジャーナルの現在の*ティップ*を含むダイジェストをリクエストします。ジャーナルのティップとは、QLDB がリクエストを受けた時点でコミット済みの最新のブロックのことです。

   `GetRevision.ts` プログラムには、次のコードが含まれています。

   ```
   /*
    * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
    * SPDX-License-Identifier: MIT-0
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this
    * software and associated documentation files (the "Software"), to deal in the Software
    * without restriction, including without limitation the rights to use, copy, modify,
    * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    * permit persons to whom the Software is furnished to do so.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
   
   import { QldbDriver, TransactionExecutor } from "amazon-qldb-driver-nodejs";
   import { QLDB } from "aws-sdk";
   import { Digest, GetDigestResponse, GetRevisionRequest, GetRevisionResponse, ValueHolder } from "aws-sdk/clients/qldb";
   import { dom, toBase64 } from "ion-js";
   
   import { getQldbDriver } from "./ConnectToLedger";
   import { getDigestResult } from './GetDigest';
   import { VEHICLE_REGISTRATION } from "./model/SampleData"
   import { blockAddressToValueHolder, getMetadataId } from './qldb/BlockAddress';
   import { LEDGER_NAME } from './qldb/Constants';
   import { error, log } from "./qldb/LogUtil";
   import { getBlobValue, valueHolderToString } from "./qldb/Util";
   import { flipRandomBit, verifyDocument } from "./qldb/Verifier";
   
   /**
    * Get the revision data object for a specified document ID and block address.
    * Also returns a proof of the specified revision for verification.
    * @param ledgerName Name of the ledger containing the document to query.
    * @param documentId Unique ID for the document to be verified, contained in the committed view of the document.
    * @param blockAddress The location of the block to request.
    * @param digestTipAddress The latest block location covered by the digest.
    * @param qldbClient The QLDB control plane client to use.
    * @returns Promise which fulfills with a GetRevisionResponse.
    */
   async function getRevision(
       ledgerName: string,
       documentId: string,
       blockAddress: ValueHolder,
       digestTipAddress: ValueHolder,
       qldbClient: QLDB
   ): Promise<GetRevisionResponse> {
       const request: GetRevisionRequest = {
           Name: ledgerName,
           BlockAddress: blockAddress,
           DocumentId: documentId,
           DigestTipAddress: digestTipAddress
       };
       const result: GetRevisionResponse = await qldbClient.getRevision(request).promise();
       return result;
   }
   
   /**
    * Query the table metadata for a particular vehicle for verification.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @param vin VIN to query the table metadata of a specific registration with.
    * @returns Promise which fulfills with a list of Ion values that contains the results of the query.
    */
   export async function lookupRegistrationForVin(txn: TransactionExecutor, vin: string): Promise<dom.Value[]> {
       log(`Querying the 'VehicleRegistration' table for VIN: ${vin}...`);
       let resultList: dom.Value[];
       const query: string = "SELECT blockAddress, metadata.id FROM _ql_committed_VehicleRegistration WHERE data.VIN = ?";
   
       await txn.execute(query, vin).then(function(result) {
         resultList = result.getResultList();
       });
       return resultList;
   }
   
   /**
    * Verify each version of the registration for the given VIN.
    * @param txn The {@linkcode TransactionExecutor} for lambda execute.
    * @param ledgerName The ledger to get the digest from.
    * @param vin VIN to query the revision history of a specific registration with.
    * @param qldbClient The QLDB control plane client to use.
    * @returns Promise which fulfills with void.
    * @throws Error: When verification fails.
    */
   export async function verifyRegistration(
       txn: TransactionExecutor, 
       ledgerName: string, 
       vin: string,
       qldbClient: QLDB
   ): Promise<void> {
       log(`Let's verify the registration with VIN = ${vin}, in ledger = ${ledgerName}.`);
       const digest: GetDigestResponse = await getDigestResult(ledgerName, qldbClient);
       const digestBytes: Digest = digest.Digest;
       const digestTipAddress: ValueHolder = digest.DigestTipAddress;
   
       log(
           `Got a ledger digest: digest tip address = \n${valueHolderToString(digestTipAddress)},
           digest = \n${toBase64(<Uint8Array> digestBytes)}.`
       );
       log(`Querying the registration with VIN = ${vin} to verify each version of the registration...`);
       const resultList: dom.Value[] = await lookupRegistrationForVin(txn, vin);
       log("Getting a proof for the document.");
   
       for (const result of resultList) {
           const blockAddress: ValueHolder =  blockAddressToValueHolder(result);
           const documentId: string = getMetadataId(result);
   
           const revisionResponse: GetRevisionResponse = await getRevision(
               ledgerName, 
               documentId, 
               blockAddress, 
               digestTipAddress,
               qldbClient
           );
   
           const revision: dom.Value = dom.load(revisionResponse.Revision.IonText);
           const documentHash: Uint8Array = getBlobValue(revision, "hash");
           const proof: ValueHolder = revisionResponse.Proof;
           log(`Got back a proof: ${valueHolderToString(proof)}.`);
   
           let verified: boolean = verifyDocument(documentHash, digestBytes, proof);
           if (!verified) {
              throw new Error("Document revision is not verified.");
           } else {
               log("Success! The document is verified.");
           }
           const alteredDocumentHash: Uint8Array = flipRandomBit(documentHash);
   
           log(
               `Flipping one bit in the document's hash and assert that the document is NOT verified.
               The altered document hash is: ${toBase64(alteredDocumentHash)}`
           );
           verified = verifyDocument(alteredDocumentHash, digestBytes, proof);
   
           if (verified) {
               throw new Error("Expected altered document hash to not be verified against digest.");
           } else {
               log("Success! As expected flipping a bit in the document hash causes verification to fail.");
           }
           log(`Finished verifying the registration with VIN = ${vin} in ledger = ${ledgerName}.`);
       }
   }
   
   /**
    * Verify the integrity of a document revision in a QLDB ledger.
    * @returns Promise which fulfills with void.
    */
   const main = async function(): Promise<void> {
       try {
           const qldbClient: QLDB = new QLDB();
           const qldbDriver: QldbDriver = getQldbDriver();
   
           const registration = VEHICLE_REGISTRATION[0];
           const vin: string = registration.VIN;
   
           await qldbDriver.executeLambda(async (txn: TransactionExecutor) => {
               await verifyRegistration(txn, LEDGER_NAME, vin, qldbClient);
           });
       } catch (e) {
           error(`Unable to verify revision: ${e}`);
       }
   }
   
   if (require.main === module) {
       main();
   }
   ```
**注記**  
`getRevision` 関数から指定したドキュメントリビジョンの証明が返されると、このプログラムはクライアント側 API を使用してそのリビジョンを検証します。

1. トランスパイルされたプログラムを実行するには、次のコマンドを入力します。

   ```
   node dist/GetRevision.js
   ```

`vehicle-registration` 台帳を使用する必要がなくなった場合は、「[ステップ 8 (オプション): リソースをクリーンアップする](getting-started.nodejs.step-8.md)」に進みます。

# ステップ 8 (オプション): リソースをクリーンアップする
<a name="getting-started.nodejs.step-8"></a>

**重要**  
サポート終了通知: 既存のお客様は、07/31/2025 のサポート終了まで Amazon QLDB を使用できます。詳細については、[「Amazon QLDB 台帳を Amazon Aurora PostgreSQL に移行する](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)」を参照してください。

`vehicle-registration` 台帳は引き続き使用できます。ただし、不要になった場合は、削除することをお勧めします。

**台帳を削除するには**

1. 次のプログラム (`DeleteLedger.ts`) を使用して `vehicle-registration` 台帳とその内容全体を削除します。

   ```
   /*
    * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
    * SPDX-License-Identifier: MIT-0
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy of this
    * software and associated documentation files (the "Software"), to deal in the Software
    * without restriction, including without limitation the rights to use, copy, modify,
    * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    * permit persons to whom the Software is furnished to do so.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    */
   
   import { isResourceNotFoundException } from "amazon-qldb-driver-nodejs";
   import { AWSError, QLDB } from "aws-sdk";
   import { DeleteLedgerRequest, DescribeLedgerRequest } from "aws-sdk/clients/qldb";
   
   import { setDeletionProtection } from "./DeletionProtection";
   import { LEDGER_NAME } from "./qldb/Constants";
   import { error, log } from "./qldb/LogUtil";
   import { sleep } from "./qldb/Util";
   
   const LEDGER_DELETION_POLL_PERIOD_MS = 20000;
   
   /**
    * Send a request to QLDB to delete the specified ledger.
    * @param ledgerName Name of the ledger to be deleted.
    * @param qldbClient The QLDB control plane client to use.
    * @returns Promise which fulfills with void.
    */
   export async function deleteLedger(ledgerName: string, qldbClient: QLDB): Promise<void> {
       log(`Attempting to delete the ledger with name: ${ledgerName}`);
       const request: DeleteLedgerRequest = {
           Name: ledgerName
       };
       await qldbClient.deleteLedger(request).promise();
       log("Success.");
   }
   
   /**
    * Wait for the ledger to be deleted.
    * @param ledgerName Name of the ledger to be deleted.
    * @param qldbClient The QLDB control plane client to use.
    * @returns Promise which fulfills with void.
    */
   export async function waitForDeleted(ledgerName: string, qldbClient: QLDB): Promise<void> {
       log("Waiting for the ledger to be deleted...");
       const request: DescribeLedgerRequest = {
           Name: ledgerName
       };
       let isDeleted: boolean = false;
       while (true) {
           await qldbClient.describeLedger(request).promise().catch((error: AWSError) => {
               if (isResourceNotFoundException(error)) {
                   isDeleted = true;
                   log("Success. Ledger is deleted.");
               }
           });
           if (isDeleted) {
               break;
           }
           log("The ledger is still being deleted. Please wait...");
           await sleep(LEDGER_DELETION_POLL_PERIOD_MS);
       }
   }
   
   /**
    * Delete a ledger.
    * @returns Promise which fulfills with void.
    */
   const main = async function(): Promise<void> {
       try {
           const qldbClient: QLDB = new QLDB();
           await setDeletionProtection(LEDGER_NAME, qldbClient, false);
           await deleteLedger(LEDGER_NAME, qldbClient);
           await waitForDeleted(LEDGER_NAME, qldbClient);
       } catch (e) {
           error(`Unable to delete the ledger: ${e}`);
       }
   }
   
   if (require.main === module) {
       main();
   }
   ```
**注記**  
台帳に対して削除保護が有効になっている場合は、QLDB API を使用して台帳を削除する前に、まず無効にする必要があります。

1. トランスパイルされたプログラムを実行するには、次のコマンドを入力します。

   ```
   node dist/DeleteLedger.js
   ```