Step 5: Modify documents in a ledger - Amazon Quantum Ledger Database (Amazon QLDB)

Step 5: Modify documents in a ledger

Now that you have data to work with, you can start making changes to documents in the vehicle-registration ledger in Amazon QLDB. In this step, the following code examples demonstrate how to run data manipulation language (DML) statements. These statements update the primary owner of one vehicle and add a secondary owner to another vehicle.

To modify documents
  1. Use the following program (TransferVehicleOwnership.ts) to update the primary owner of the vehicle with VIN 1N4AL11D75C109151 in your ledger.

    /* * 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(); }
  2. To run the transpiled program, enter the following command.

    node dist/TransferVehicleOwnership.js
  3. Use the following program (AddSecondaryOwner.ts) to add a secondary owner to the vehicle with VIN KM8SRDHF6EU074761 in your ledger.

    /* * 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(); }
  4. To run the transpiled program, enter the following command.

    node dist/AddSecondaryOwner.js

To review these changes in the vehicle-registration ledger, see Step 6: View the revision history for a document.