

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

# Tutorial Python QLDB Amazon
<a name="getting-started.python.tutorial"></a>

**penting**  
Pemberitahuan akhir dukungan: Pelanggan yang ada akan dapat menggunakan Amazon QLDB hingga akhir dukungan pada 07/31/2025. Untuk detail selengkapnya, lihat [Memigrasi Buku Besar QLDB Amazon ke Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

Dalam implementasi aplikasi contoh tutorial ini, Anda menggunakan driver Amazon QLDB dengan AWS SDK untuk Python (Boto3) untuk membuat buku besar QLDB dan mengisinya dengan data sampel.

Saat Anda mengerjakan tutorial ini, Anda dapat merujuk ke klien [tingkat rendah QLDB](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/qldb.html) di *AWS SDK for Python (Boto3)* API Referensi untuk operasi API manajemen. Untuk operasi data transaksional, Anda dapat merujuk ke [Driver QLDB](https://amazon-qldb-driver-python.readthedocs.io/en/stable/) untuk Referensi API Python.

**catatan**  
Jika berlaku, beberapa langkah tutorial memiliki perintah atau contoh kode yang berbeda untuk setiap versi utama yang didukung dari driver QLDB untuk Python.

**Topics**
+ [Menginstal aplikasi sampel Amazon QLDB Python](sample-app.python.md)
+ [Langkah 1: Buat buku besar baru](getting-started.python.step-1.md)
+ [Langkah 2: Uji konektivitas ke buku besar](getting-started.python.step-2.md)
+ [Langkah 3: Buat tabel, indeks, dan data sampel](getting-started.python.step-3.md)
+ [Langkah 4: Kueri tabel dalam buku besar](getting-started.python.step-4.md)
+ [Langkah 5: Ubah dokumen dalam buku besar](getting-started.python.step-5.md)
+ [Langkah 6: Lihat riwayat revisi untuk dokumen](getting-started.python.step-6.md)
+ [Langkah 7: Verifikasi dokumen dalam buku besar](getting-started.python.step-7.md)
+ [Langkah 8 (opsional): Bersihkan sumber daya](getting-started.python.step-8.md)

# Menginstal aplikasi sampel Amazon QLDB Python
<a name="sample-app.python"></a>

**penting**  
Pemberitahuan akhir dukungan: Pelanggan yang ada akan dapat menggunakan Amazon QLDB hingga akhir dukungan pada 07/31/2025. Untuk detail selengkapnya, lihat [Memigrasi Buku Besar QLDB Amazon ke Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

Bagian ini menjelaskan cara menginstal dan menjalankan aplikasi sampel Amazon QLDB yang disediakan untuk tutorial Python. step-by-step Kasus penggunaan untuk aplikasi sampel ini adalah database departemen kendaraan bermotor (DMV) yang melacak informasi historis lengkap tentang pendaftaran kendaraan.

[Contoh aplikasi DMV untuk Python adalah open source di repositori aws-samples/ -python. GitHub amazon-qldb-dmv-sample](https://github.com/aws-samples/amazon-qldb-dmv-sample-python)

## Prasyarat
<a name="sample-app.python.prereqs"></a>

Sebelum Anda memulai, pastikan Anda menyelesaikan driver QLDB untuk Python. [Prasyarat](getting-started.python.md#getting-started.python.prereqs) Ini termasuk menginstal Python dan melakukan hal berikut:

1. Mendaftar untuk AWS.

1. Buat pengguna dengan izin QLDB yang sesuai.

1. Memberikan akses terprogram untuk pengembangan.

Untuk menyelesaikan semua langkah dalam tutorial ini, Anda memerlukan akses administratif penuh ke sumber daya buku besar Anda melalui QLDB API.

## Penginstalan
<a name="sample-app.python.install"></a>

**Untuk menginstal aplikasi sampel**

1. Masukkan `pip` perintah berikut untuk mengkloning dan menginstal aplikasi sampel dari GitHub.

------
#### [ 3.x ]

   ```
   pip install git+https://github.com/aws-samples/amazon-qldb-dmv-sample-python.git
   ```

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

   ```
   pip install git+https://github.com/aws-samples/amazon-qldb-dmv-sample-python.git@v1.0.0
   ```

------

   Aplikasi sampel mengemas kode sumber lengkap dari tutorial ini dan dependensinya, termasuk driver Python dan file. [AWS SDK untuk Python (Boto3)](https://aws.amazon.com/sdk-for-python)

1. Sebelum Anda mulai menjalankan kode di baris perintah, alihkan direktori kerja Anda saat ini ke lokasi di mana `pyqldbsamples` paket diinstal. Masukkan perintah berikut.

   ```
   cd $(python -c "import pyqldbsamples; print(pyqldbsamples.__path__[0])")
   ```

1. Lanjutkan [Langkah 1: Buat buku besar baru](getting-started.python.step-1.md) untuk memulai tutorial dan membuat buku besar.

# Langkah 1: Buat buku besar baru
<a name="getting-started.python.step-1"></a>

**penting**  
Pemberitahuan akhir dukungan: Pelanggan yang ada akan dapat menggunakan Amazon QLDB hingga akhir dukungan pada 07/31/2025. Untuk detail selengkapnya, lihat [Memigrasi Buku Besar QLDB Amazon ke Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

Pada langkah ini, Anda membuat buku besar Amazon QLDB baru bernama. `vehicle-registration`

**Untuk membuat buku besar baru**

1. Tinjau file berikut (`constants.py`), yang berisi nilai konstan yang digunakan oleh semua program lain dalam tutorial ini.

   ```
   # 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.
   
   
   class Constants:
       """
       Constant values used throughout this tutorial.
       """
       LEDGER_NAME = "vehicle-registration"
   
       VEHICLE_REGISTRATION_TABLE_NAME = "VehicleRegistration"
       VEHICLE_TABLE_NAME = "Vehicle"
       PERSON_TABLE_NAME = "Person"
       DRIVERS_LICENSE_TABLE_NAME = "DriversLicense"
   
       LICENSE_NUMBER_INDEX_NAME = "LicenseNumber"
       GOV_ID_INDEX_NAME = "GovId"
       VEHICLE_VIN_INDEX_NAME = "VIN"
       LICENSE_PLATE_NUMBER_INDEX_NAME = "LicensePlateNumber"
       PERSON_ID_INDEX_NAME = "PersonId"
   
       JOURNAL_EXPORT_S3_BUCKET_NAME_PREFIX = "qldb-tutorial-journal-export"
       USER_TABLES = "information_schema.user_tables"
       S3_BUCKET_ARN_TEMPLATE = "arn:aws:s3:::"
       LEDGER_NAME_WITH_TAGS = "tags"
   
       RETRY_LIMIT = 4
   ```

1. Gunakan program berikut (`create_ledger.py`) untuk membuat buku besar bernama`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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   from time import sleep
   
   from boto3 import client
   
   from pyqldbsamples.constants import Constants
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   qldb_client = client('qldb')
   
   LEDGER_CREATION_POLL_PERIOD_SEC = 10
   ACTIVE_STATE = "ACTIVE"
   
   
   def create_ledger(name):
       """
       Create a new ledger with the specified name.
   
       :type name: str
       :param name: Name for the ledger to be created.
   
       :rtype: dict
       :return: Result from the request.
       """
       logger.info("Let's create the ledger named: {}...".format(name))
       result = qldb_client.create_ledger(Name=name, PermissionsMode='ALLOW_ALL')
       logger.info('Success. Ledger state: {}.'.format(result.get('State')))
       return result
   
   
   def wait_for_active(name):
       """
       Wait for the newly created ledger to become active.
   
       :type name: str
       :param name: The ledger to check on.
   
       :rtype: dict
       :return: Result from the request.
       """
       logger.info('Waiting for ledger to become active...')
       while True:
           result = qldb_client.describe_ledger(Name=name)
           if result.get('State') == ACTIVE_STATE:
               logger.info('Success. Ledger is active and ready to use.')
               return result
           logger.info('The ledger is still creating. Please wait...')
           sleep(LEDGER_CREATION_POLL_PERIOD_SEC)
   
   
   def main(ledger_name=Constants.LEDGER_NAME):
       """
       Create a ledger and wait for it to be active.
       """
       try:
           create_ledger(ledger_name)
           wait_for_active(ledger_name)
       except Exception as e:
           logger.exception('Unable to create the ledger!')
           raise e
   
   
   if __name__ == '__main__':
       main()
   ```
**catatan**  
Dalam `create_ledger` panggilan, Anda harus menentukan nama buku besar dan mode izin. Sebaiknya gunakan mode `STANDARD` izin untuk memaksimalkan keamanan data buku besar Anda.
Saat Anda membuat buku besar, *perlindungan penghapusan* diaktifkan secara default. Ini adalah fitur di QLDB yang mencegah buku besar dihapus oleh pengguna mana pun. Anda memiliki opsi untuk menonaktifkan perlindungan penghapusan pada pembuatan buku besar menggunakan QLDB API atau (). AWS Command Line Interface AWS CLI
Secara opsional, Anda juga dapat menentukan tag untuk dilampirkan ke buku besar Anda.

1. Untuk menjalankan program, masukkan perintah berikut.

   ```
   python create_ledger.py
   ```

Untuk memverifikasi koneksi Anda ke buku besar baru, lanjutkan ke[Langkah 2: Uji konektivitas ke buku besar](getting-started.python.step-2.md).

# Langkah 2: Uji konektivitas ke buku besar
<a name="getting-started.python.step-2"></a>

**penting**  
Pemberitahuan akhir dukungan: Pelanggan yang ada akan dapat menggunakan Amazon QLDB hingga akhir dukungan pada 07/31/2025. Untuk detail selengkapnya, lihat [Memigrasi Buku Besar QLDB Amazon ke Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

Pada langkah ini, Anda memverifikasi bahwa Anda dapat terhubung ke `vehicle-registration` buku besar di Amazon QLDB menggunakan titik akhir API data transaksional.

**Untuk menguji konektivitas ke buku besar**

1. Gunakan program berikut (`connect_to_ledger.py`) untuk membuat koneksi sesi data ke `vehicle-registration` buku besar.

------
#### [ 3.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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from botocore.exceptions import ClientError
   
   from pyqldb.driver.qldb_driver import QldbDriver
   from pyqldbsamples.constants import Constants
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def create_qldb_driver(ledger_name=Constants.LEDGER_NAME, region_name=None, endpoint_url=None, boto3_session=None):
       """
       Create a QLDB driver for executing transactions.
   
       :type ledger_name: str
       :param ledger_name: The QLDB ledger name.
   
       :type region_name: str
       :param region_name: See [1].
   
       :type endpoint_url: str
       :param endpoint_url: See [1].
   
       :type boto3_session: :py:class:`boto3.session.Session`
       :param boto3_session: The boto3 session to create the client with (see [1]).
   
       :rtype: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :return: A QLDB driver object.
   
       [1]: `Boto3 Session.client Reference <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/core/session.html#boto3.session.Session.client>`.
       """
       qldb_driver = QldbDriver(ledger_name=ledger_name, region_name=region_name, endpoint_url=endpoint_url,
                                boto3_session=boto3_session)
       return qldb_driver
   
   
   def main(ledger_name=Constants.LEDGER_NAME):
       """
       Connect to a given ledger using default settings.
       """
       try:
           with create_qldb_driver(ledger_name) as driver:
               logger.info('Listing table names ')
               for table in driver.list_tables():
                   logger.info(table)
       except ClientError as ce:
           logger.exception('Unable to list tables.')
           raise ce
   
   
   if __name__ == '__main__':
       main()
   ```

**catatan**  
Untuk menjalankan transaksi data pada buku besar Anda, Anda harus membuat objek driver QLDB untuk terhubung ke buku besar tertentu. Ini adalah objek klien yang berbeda dari `qldb_client` objek yang Anda gunakan pada langkah sebelumnya untuk membuat buku besar. Klien sebelumnya hanya digunakan untuk menjalankan operasi API manajemen yang tercantum dalam[Referensi API QLDB Amazon](api-reference.md).
Anda harus menentukan nama buku besar ketika Anda membuat objek driver ini.

------
#### [ 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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from botocore.exceptions import ClientError
   
   from pyqldb.driver.pooled_qldb_driver import PooledQldbDriver
   from pyqldbsamples.constants import Constants
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def create_qldb_driver(ledger_name=Constants.LEDGER_NAME, region_name=None, endpoint_url=None, boto3_session=None):
       """
       Create a QLDB driver for creating sessions.
   
       :type ledger_name: str
       :param ledger_name: The QLDB ledger name.
   
       :type region_name: str
       :param region_name: See [1].
   
       :type endpoint_url: str
       :param endpoint_url: See [1].
   
       :type boto3_session: :py:class:`boto3.session.Session`
       :param boto3_session: The boto3 session to create the client with (see [1]).
   
       :rtype: :py:class:`pyqldb.driver.pooled_qldb_driver.PooledQldbDriver`
       :return: A pooled QLDB driver object.
   
       [1]: `Boto3 Session.client Reference <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/core/session.html#boto3.session.Session.client>`.
       """
       qldb_driver = PooledQldbDriver(ledger_name=ledger_name, region_name=region_name, endpoint_url=endpoint_url,
                                      boto3_session=boto3_session)
       return qldb_driver
   
   
   def create_qldb_session():
       """
       Retrieve a QLDB session object.
   
       :rtype: :py:class:`pyqldb.session.pooled_qldb_session.PooledQldbSession`
       :return: A pooled QLDB session object.
       """
       qldb_session = pooled_qldb_driver.get_session()
       return qldb_session
   
   
   pooled_qldb_driver = create_qldb_driver()
   
   
   if __name__ == '__main__':
       """
       Connect to a session for a given ledger using default settings.
       """
       try:
           qldb_session = create_qldb_session()
           logger.info('Listing table names ')
           for table in qldb_session.list_tables():
               logger.info(table)
       except ClientError:
           logger.exception('Unable to create session.')
   ```

**catatan**  
Untuk menjalankan transaksi data pada buku besar Anda, Anda harus membuat objek driver QLDB untuk terhubung ke buku besar tertentu. Ini adalah objek klien yang berbeda dari `qldb_client` objek yang Anda gunakan pada langkah sebelumnya untuk membuat buku besar. Klien sebelumnya hanya digunakan untuk menjalankan operasi API manajemen yang tercantum dalam[Referensi API QLDB Amazon](api-reference.md).
Pertama, buat objek driver QLDB yang dikumpulkan. Anda harus menentukan nama buku besar saat Anda membuat driver ini.
Kemudian, Anda dapat membuat sesi dari objek driver yang dikumpulkan ini.

------

1. Untuk menjalankan program, masukkan perintah berikut.

   ```
   python connect_to_ledger.py
   ```

Untuk membuat tabel di `vehicle-registration` buku besar, lanjutkan ke[Langkah 3: Buat tabel, indeks, dan data sampel](getting-started.python.step-3.md).

# Langkah 3: Buat tabel, indeks, dan data sampel
<a name="getting-started.python.step-3"></a>

**penting**  
Pemberitahuan akhir dukungan: Pelanggan yang ada akan dapat menggunakan Amazon QLDB hingga akhir dukungan pada 07/31/2025. Untuk detail selengkapnya, lihat [Memigrasi Buku Besar QLDB Amazon ke Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

Saat buku besar QLDB Amazon Anda aktif dan menerima koneksi, Anda dapat mulai membuat tabel untuk data tentang kendaraan, pemiliknya, dan informasi pendaftarannya. Setelah membuat tabel dan indeks, Anda dapat memuatnya dengan data.

Pada langkah ini, Anda membuat empat tabel di `vehicle-registration` buku besar:
+ `VehicleRegistration`
+ `Vehicle`
+ `Person`
+ `DriversLicense`

Anda juga membuat indeks berikut.


****  

| Nama tabel | Bidang | 
| --- | --- | 
| VehicleRegistration | VIN | 
| VehicleRegistration | LicensePlateNumber | 
| Vehicle | VIN | 
| Person | GovId | 
| DriversLicense | LicenseNumber | 
| DriversLicense | PersonId | 

Saat memasukkan data sampel, Anda terlebih dahulu memasukkan dokumen ke dalam `Person` tabel. Kemudian, Anda menggunakan sistem yang ditetapkan `id` dari setiap `Person` dokumen untuk mengisi bidang yang sesuai dalam dokumen yang sesuai`VehicleRegistration`. `DriversLicense`

**Tip**  
Sebagai praktik terbaik, gunakan sistem dokumen yang ditugaskan `id` sebagai kunci asing. Meskipun Anda dapat menentukan bidang yang dimaksudkan untuk menjadi pengidentifikasi unik (misalnya, VIN kendaraan), pengidentifikasi unik sebenarnya dari dokumen adalah miliknya. `id` Bidang ini disertakan dalam metadata dokumen, yang dapat Anda kueri dalam tampilan *komited (tampilan* tabel yang ditentukan sistem).  
Untuk informasi selengkapnya tentang tampilan di QLDB, lihat. [Konsep inti](ledger-structure.md) Untuk mempelajari lebih lanjut tentang metadata, lihat. [Meminta metadata dokumen](working.metadata.md)

**Untuk membuat tabel dan indeks**

1. Gunakan program berikut (`create_table.py`) untuk membuat tabel yang disebutkan sebelumnya.

------
#### [ 3.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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.connect_to_ledger import create_qldb_driver
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def create_table(driver, table_name):
       """
       Create a table with the specified name.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
   
       :type table_name: str
       :param table_name: Name of the table to create.
   
       :rtype: int
       :return: The number of changes to the database.
       """
       logger.info("Creating the '{}' table...".format(table_name))
       statement = 'CREATE TABLE {}'.format(table_name)
       cursor = driver.execute_lambda(lambda executor: executor.execute_statement(statement))
       logger.info('{} table created successfully.'.format(table_name))
       return len(list(cursor))
   
   
   def main(ledger_name=Constants.LEDGER_NAME):
       """
       Create registrations, vehicles, owners, and licenses tables.
       """
       try:
           with create_qldb_driver(ledger_name) as driver:
               create_table(driver, Constants.DRIVERS_LICENSE_TABLE_NAME)
               create_table(driver, Constants.PERSON_TABLE_NAME)
               create_table(driver, Constants.VEHICLE_TABLE_NAME)
               create_table(driver, Constants.VEHICLE_REGISTRATION_TABLE_NAME)
               logger.info('Tables created successfully.')
       except Exception as e:
           logger.exception('Errors creating tables.')
           raise e
   
   
   if __name__ == '__main__':
       main()
   ```

------
#### [ 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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.connect_to_ledger import create_qldb_session
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def create_table(transaction_executor, table_name):
       """
       Create a table with the specified name using an Executor object.
   
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
   
       :type table_name: str
       :param table_name: Name of the table to create.
   
       :rtype: int
       :return: The number of changes to the database.
       """
       logger.info("Creating the '{}' table...".format(table_name))
       statement = 'CREATE TABLE {}'.format(table_name)
       cursor = transaction_executor.execute_statement(statement)
       logger.info('{} table created successfully.'.format(table_name))
       return len(list(cursor))
   
   
   if __name__ == '__main__':
       """
       Create registrations, vehicles, owners, and licenses tables in a single transaction.
       """
       try:
           with create_qldb_session() as session:
               session.execute_lambda(lambda x: create_table(x, Constants.DRIVERS_LICENSE_TABLE_NAME) and
                                      create_table(x, Constants.PERSON_TABLE_NAME) and
                                      create_table(x, Constants.VEHICLE_TABLE_NAME) and
                                      create_table(x, Constants.VEHICLE_REGISTRATION_TABLE_NAME),
                                      lambda retry_attempt: logger.info('Retrying due to OCC conflict...'))
               logger.info('Tables created successfully.')
       except Exception:
           logger.exception('Errors creating tables.')
   ```

------
**catatan**  
Program ini menunjukkan cara menggunakan `execute_lambda` fungsi. Dalam contoh ini, Anda menjalankan beberapa pernyataan `CREATE TABLE` PartiQL dengan ekspresi lambda tunggal.  
Fungsi eksekusi ini secara implisit memulai transaksi, menjalankan semua pernyataan di lambda, dan kemudian melakukan transaksi secara otomatis.

1. Untuk menjalankan program, masukkan perintah berikut.

   ```
   python create_table.py
   ```

1. Gunakan program berikut (`create_index.py`) untuk membuat indeks pada tabel, seperti yang dijelaskan sebelumnya.

------
#### [ 3.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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.connect_to_ledger import create_qldb_driver
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def create_index(driver, table_name, index_attribute):
       """
       Create an index for a particular table.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
   
       :type table_name: str
       :param table_name: Name of the table to add indexes for.
   
       :type index_attribute: str
       :param index_attribute: Index to create on a single attribute.
   
       :rtype: int
       :return: The number of changes to the database.
       """
       logger.info("Creating index on '{}'...".format(index_attribute))
       statement = 'CREATE INDEX on {} ({})'.format(table_name, index_attribute)
       cursor = driver.execute_lambda(lambda executor: executor.execute_statement(statement))
       return len(list(cursor))
   
   
   def main(ledger_name=Constants.LEDGER_NAME):
       """
       Create indexes on tables in a particular ledger.
       """
       logger.info('Creating indexes on all tables...')
       try:
           with create_qldb_driver(ledger_name) as driver:
               create_index(driver, Constants.PERSON_TABLE_NAME, Constants.GOV_ID_INDEX_NAME)
               create_index(driver, Constants.VEHICLE_TABLE_NAME, Constants.VEHICLE_VIN_INDEX_NAME)
               create_index(driver, Constants.VEHICLE_REGISTRATION_TABLE_NAME, Constants.LICENSE_PLATE_NUMBER_INDEX_NAME)
               create_index(driver, Constants.VEHICLE_REGISTRATION_TABLE_NAME, Constants.VEHICLE_VIN_INDEX_NAME)
               create_index(driver, Constants.DRIVERS_LICENSE_TABLE_NAME, Constants.PERSON_ID_INDEX_NAME)
               create_index(driver, Constants.DRIVERS_LICENSE_TABLE_NAME, Constants.LICENSE_NUMBER_INDEX_NAME)
               logger.info('Indexes created successfully.')
       except Exception as e:
           logger.exception('Unable to create indexes.')
           raise e
   
   
   if __name__ == '__main__':
       main()
   ```

------
#### [ 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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.connect_to_ledger import create_qldb_session
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def create_index(transaction_executor, table_name, index_attribute):
       """
       Create an index for a particular table.
   
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
   
       :type table_name: str
       :param table_name: Name of the table to add indexes for.
   
       :type index_attribute: str
       :param index_attribute: Index to create on a single attribute.
   
       :rtype: int
       :return: The number of changes to the database.
       """
       logger.info("Creating index on '{}'...".format(index_attribute))
       statement = 'CREATE INDEX on {} ({})'.format(table_name, index_attribute)
       cursor = transaction_executor.execute_statement(statement)
       return len(list(cursor))
   
   
   if __name__ == '__main__':
       """
       Create indexes on tables in a particular ledger.
       """
       logger.info('Creating indexes on all tables in a single transaction...')
       try:
           with create_qldb_session() as session:
               session.execute_lambda(lambda x: create_index(x, Constants.PERSON_TABLE_NAME,
                                                             Constants.GOV_ID_INDEX_NAME)
                                      and create_index(x, Constants.VEHICLE_TABLE_NAME,
                                                       Constants.VEHICLE_VIN_INDEX_NAME)
                                      and create_index(x, Constants.VEHICLE_REGISTRATION_TABLE_NAME,
                                                       Constants.LICENSE_PLATE_NUMBER_INDEX_NAME)
                                      and create_index(x, Constants.VEHICLE_REGISTRATION_TABLE_NAME,
                                                       Constants.VEHICLE_VIN_INDEX_NAME)
                                      and create_index(x, Constants.DRIVERS_LICENSE_TABLE_NAME,
                                                       Constants.PERSON_ID_INDEX_NAME)
                                      and create_index(x, Constants.DRIVERS_LICENSE_TABLE_NAME,
                                                       Constants.LICENSE_NUMBER_INDEX_NAME),
                                      lambda retry_attempt: logger.info('Retrying due to OCC conflict...'))
               logger.info('Indexes created successfully.')
       except Exception:
           logger.exception('Unable to create indexes.')
   ```

------

1. Untuk menjalankan program, masukkan perintah berikut.

   ```
   python create_index.py
   ```

**Untuk memuat data ke dalam tabel**

1. Tinjau file berikut (`sample_data.py`), yang mewakili data sampel yang Anda masukkan ke dalam `vehicle-registration` tabel. File ini juga mengimpor dari `amazon.ion` paket untuk menyediakan fungsi pembantu yang mengonversi, mengurai, dan mencetak data [Amazon](ion.md) Ion. 

   ```
   # 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.
   from datetime import datetime
   from decimal import Decimal
   from logging import basicConfig, getLogger, INFO
   
   from amazon.ion.simple_types import IonPyBool, IonPyBytes, IonPyDecimal, IonPyDict, IonPyFloat, IonPyInt, IonPyList, \
       IonPyNull, IonPySymbol, IonPyText, IonPyTimestamp
   from amazon.ion.simpleion import dumps, loads
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   IonValue = (IonPyBool, IonPyBytes, IonPyDecimal, IonPyDict, IonPyFloat, IonPyInt, IonPyList, IonPyNull, IonPySymbol,
               IonPyText, IonPyTimestamp)
   
   
   class SampleData:
       """
       Sample domain objects for use throughout this tutorial.
       """
       DRIVERS_LICENSE = [
           {
               'PersonId': '',
               'LicenseNumber': 'LEWISR261LL',
               'LicenseType': 'Learner',
               'ValidFromDate': datetime(2016, 12, 20),
               'ValidToDate': datetime(2020, 11, 15)
           },
           {
               'PersonId': '',
               'LicenseNumber': 'LOGANB486CG',
               'LicenseType': 'Probationary',
               'ValidFromDate': datetime(2016, 4, 6),
               'ValidToDate': datetime(2020, 11, 15)
           },
           {
               'PersonId': '',
               'LicenseNumber': '744 849 301',
               'LicenseType': 'Full',
               'ValidFromDate': datetime(2017, 12, 6),
               'ValidToDate': datetime(2022, 10, 15)
           },
           {
               'PersonId': '',
               'LicenseNumber': 'P626-168-229-765',
               'LicenseType': 'Learner',
               'ValidFromDate': datetime(2017, 8, 16),
               'ValidToDate': datetime(2021, 11, 15)
           },
           {
               'PersonId': '',
               'LicenseNumber': 'S152-780-97-415-0',
               'LicenseType': 'Probationary',
               'ValidFromDate': datetime(2015, 8, 15),
               'ValidToDate': datetime(2021, 8, 21)
           }
       ]
       PERSON = [
           {
               'FirstName': 'Raul',
               'LastName': 'Lewis',
               'Address': '1719 University Street, Seattle, WA, 98109',
               'DOB': datetime(1963, 8, 19),
               'GovId': 'LEWISR261LL',
               'GovIdType': 'Driver License'
           },
           {
               'FirstName': 'Brent',
               'LastName': 'Logan',
               'DOB': datetime(1967, 7, 3),
               'Address': '43 Stockert Hollow Road, Everett, WA, 98203',
               'GovId': 'LOGANB486CG',
               'GovIdType': 'Driver License'
           },
           {
               'FirstName': 'Alexis',
               'LastName': 'Pena',
               'DOB': datetime(1974, 2, 10),
               'Address': '4058 Melrose Street, Spokane Valley, WA, 99206',
               'GovId': '744 849 301',
               'GovIdType': 'SSN'
           },
           {
               'FirstName': 'Melvin',
               'LastName': 'Parker',
               'DOB': datetime(1976, 5, 22),
               'Address': '4362 Ryder Avenue, Seattle, WA, 98101',
               'GovId': 'P626-168-229-765',
               'GovIdType': 'Passport'
           },
           {
               'FirstName': 'Salvatore',
               'LastName': 'Spencer',
               'DOB': datetime(1997, 11, 15),
               'Address': '4450 Honeysuckle Lane, Seattle, WA, 98101',
               'GovId': 'S152-780-97-415-0',
               'GovIdType': 'Passport'
           }
       ]
       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'
           }
       ]
       VEHICLE_REGISTRATION = [
           {
               'VIN': '1N4AL11D75C109151',
               'LicensePlateNumber': 'LEWISR261LL',
               'State': 'WA',
               'City': 'Seattle',
               'ValidFromDate': datetime(2017, 8, 21),
               'ValidToDate': datetime(2020, 5, 11),
               'PendingPenaltyTicketAmount': Decimal('90.25'),
               'Owners': {
                   'PrimaryOwner': {'PersonId': ''},
                   'SecondaryOwners': []
               }
           },
           {
               'VIN': 'KM8SRDHF6EU074761',
               'LicensePlateNumber': 'CA762X',
               'State': 'WA',
               'City': 'Kent',
               'PendingPenaltyTicketAmount': Decimal('130.75'),
               'ValidFromDate': datetime(2017, 9, 14),
               'ValidToDate': datetime(2020, 6, 25),
               'Owners': {
                   'PrimaryOwner': {'PersonId': ''},
                   'SecondaryOwners': []
               }
           },
           {
               'VIN': '3HGGK5G53FM761765',
               'LicensePlateNumber': 'CD820Z',
               'State': 'WA',
               'City': 'Everett',
               'PendingPenaltyTicketAmount': Decimal('442.30'),
               'ValidFromDate': datetime(2011, 3, 17),
               'ValidToDate': datetime(2021, 3, 24),
               'Owners': {
                   'PrimaryOwner': {'PersonId': ''},
                   'SecondaryOwners': []
               }
           },
           {
               'VIN': '1HVBBAANXWH544237',
               'LicensePlateNumber': 'LS477D',
               'State': 'WA',
               'City': 'Tacoma',
               'PendingPenaltyTicketAmount': Decimal('42.20'),
               'ValidFromDate': datetime(2011, 10, 26),
               'ValidToDate': datetime(2023, 9, 25),
               'Owners': {
                   'PrimaryOwner': {'PersonId': ''},
                   'SecondaryOwners': []
               }
           },
           {
               'VIN': '1C4RJFAG0FC625797',
               'LicensePlateNumber': 'TH393F',
               'State': 'WA',
               'City': 'Olympia',
               'PendingPenaltyTicketAmount': Decimal('30.45'),
               'ValidFromDate': datetime(2013, 9, 2),
               'ValidToDate': datetime(2024, 3, 19),
               'Owners': {
                   'PrimaryOwner': {'PersonId': ''},
                   'SecondaryOwners': []
               }
           }
       ]
   
   
   def convert_object_to_ion(py_object):
       """
       Convert a Python object into an Ion object.
   
       :type py_object: object
       :param py_object: The object to convert.
   
       :rtype: :py:class:`amazon.ion.simple_types.IonPyValue`
       :return: The converted Ion object.
       """
       ion_object = loads(dumps(py_object))
       return ion_object
   
   
   def to_ion_struct(key, value):
       """
       Convert the given key and value into an Ion struct.
   
       :type key: str
       :param key: The key which serves as an unique identifier.
   
       :type value: str
       :param value: The value associated with a given key.
   
       :rtype: :py:class:`amazon.ion.simple_types.IonPyDict`
       :return: The Ion dictionary object.
       """
       ion_struct = dict()
       ion_struct[key] = value
       return loads(str(ion_struct))
   
   
   def get_document_ids(transaction_executor, table_name, field, value):
       """
       Gets the document IDs from the given table.
   
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
   
       :type table_name: str
       :param table_name: The table name to query.
   
       :type field: str
       :param field: A field to query.
   
       :type value: str
       :param value: The key of the given field.
   
       :rtype: list
       :return: A list of document IDs.
       """
       query = "SELECT id FROM {} AS t BY id WHERE t.{} = ?".format(table_name, field)
       cursor = transaction_executor.execute_statement(query, convert_object_to_ion(value))
       return list(map(lambda table: table.get('id'), cursor))
   
   
   def get_document_ids_from_dml_results(result):
       """
       Return a list of modified document IDs as strings from DML results.
   
       :type result: :py:class:`pyqldb.cursor.buffered_cursor.BufferedCursor`
       :param: result: The result set from DML operation.
   
       :rtype: list
       :return: List of document IDs.
       """
       ret_val = list(map(lambda x: x.get('documentId'), result))
       return ret_val
   
   
   def print_result(cursor):
       """
       Pretty print the result set. Returns the number of documents in the result set.
   
       :type cursor: :py:class:`pyqldb.cursor.stream_cursor.StreamCursor`/
                     :py:class:`pyqldb.cursor.buffered_cursor.BufferedCursor`
       :param cursor: An instance of the StreamCursor or BufferedCursor class.
   
       :rtype: int
       :return: Number of documents in the result set.
       """
       result_counter = 0
       for row in cursor:
           # Each row would be in Ion format.
           print_ion(row)
           result_counter += 1
       return result_counter
   
   
   def print_ion(ion_value):
       """
       Pretty print an Ion Value.
   
       :type ion_value: :py:class:`amazon.ion.simple_types.IonPySymbol`
       :param ion_value: Any Ion Value to be pretty printed.
       """
       logger.info(dumps(ion_value, binary=False, indent='  ', omit_version_marker=True))
   ```
**catatan**  
`get_document_ids`Fungsi ini menjalankan query yang mengembalikan dokumen system-assigned IDs dari tabel. Untuk mempelajari selengkapnya, lihat [Menggunakan klausa BY untuk menanyakan ID dokumen](working.metadata.by-clause.md).

1. Gunakan program berikut (`insert_document.py`) untuk menyisipkan data sampel ke dalam tabel Anda.

------
#### [ 3.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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.model.sample_data import convert_object_to_ion, SampleData, get_document_ids_from_dml_results
   from pyqldbsamples.connect_to_ledger import create_qldb_driver
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def update_person_id(document_ids):
       """
       Update the PersonId value for DriversLicense records and the PrimaryOwner value for VehicleRegistration records.
   
       :type document_ids: list
       :param document_ids: List of document IDs.
   
       :rtype: list
       :return: Lists of updated DriversLicense records and updated VehicleRegistration records.
       """
       new_drivers_licenses = SampleData.DRIVERS_LICENSE.copy()
       new_vehicle_registrations = SampleData.VEHICLE_REGISTRATION.copy()
       for i in range(len(SampleData.PERSON)):
           drivers_license = new_drivers_licenses[i]
           registration = new_vehicle_registrations[i]
           drivers_license.update({'PersonId': str(document_ids[i])})
           registration['Owners']['PrimaryOwner'].update({'PersonId': str(document_ids[i])})
       return new_drivers_licenses, new_vehicle_registrations
   
   
   def insert_documents(driver, table_name, documents):
       """
       Insert the given list of documents into a table in a single transaction.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
   
       :type table_name: str
       :param table_name: Name of the table to insert documents into.
   
       :type documents: list
       :param documents: List of documents to insert.
   
       :rtype: list
       :return: List of documents IDs for the newly inserted documents.
       """
       logger.info('Inserting some documents in the {} table...'.format(table_name))
       statement = 'INSERT INTO {} ?'.format(table_name)
       cursor = driver.execute_lambda(lambda executor: executor.execute_statement(statement,
                                                                                  convert_object_to_ion(documents)))
       list_of_document_ids = get_document_ids_from_dml_results(cursor)
   
       return list_of_document_ids
   
   
   def update_and_insert_documents(driver):
       """
       Handle the insertion of documents and updating PersonIds.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
       """
       list_ids = insert_documents(driver, Constants.PERSON_TABLE_NAME, SampleData.PERSON)
   
       logger.info("Updating PersonIds for 'DriversLicense' and PrimaryOwner for 'VehicleRegistration'...")
       new_licenses, new_registrations = update_person_id(list_ids)
   
       insert_documents(driver, Constants.VEHICLE_TABLE_NAME, SampleData.VEHICLE)
       insert_documents(driver, Constants.VEHICLE_REGISTRATION_TABLE_NAME, new_registrations)
       insert_documents(driver, Constants.DRIVERS_LICENSE_TABLE_NAME, new_licenses)
   
   
   def main(ledger_name=Constants.LEDGER_NAME):
       """
       Insert documents into a table in a QLDB ledger.
       """
       try:
           with create_qldb_driver(ledger_name) as driver:
               # An INSERT statement creates the initial revision of a document with a version number of zero.
               # QLDB also assigns a unique document identifier in GUID format as part of the metadata.
               update_and_insert_documents(driver)
               logger.info('Documents inserted successfully!')
       except Exception as e:
           logger.exception('Error inserting or updating documents.')
           raise e
   
   
   if __name__ == '__main__':
       main()
   ```

------
#### [ 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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.model.sample_data import convert_object_to_ion, SampleData, get_document_ids_from_dml_results
   from pyqldbsamples.connect_to_ledger import create_qldb_session
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def update_person_id(document_ids):
       """
       Update the PersonId value for DriversLicense records and the PrimaryOwner value for VehicleRegistration records.
   
       :type document_ids: list
       :param document_ids: List of document IDs.
   
       :rtype: list
       :return: Lists of updated DriversLicense records and updated VehicleRegistration records.
       """
       new_drivers_licenses = SampleData.DRIVERS_LICENSE.copy()
       new_vehicle_registrations = SampleData.VEHICLE_REGISTRATION.copy()
       for i in range(len(SampleData.PERSON)):
           drivers_license = new_drivers_licenses[i]
           registration = new_vehicle_registrations[i]
           drivers_license.update({'PersonId': str(document_ids[i])})
           registration['Owners']['PrimaryOwner'].update({'PersonId': str(document_ids[i])})
       return new_drivers_licenses, new_vehicle_registrations
   
   
   def insert_documents(transaction_executor, table_name, documents):
       """
       Insert the given list of documents into a table in a single transaction.
   
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
   
       :type table_name: str
       :param table_name: Name of the table to insert documents into.
   
       :type documents: list
       :param documents: List of documents to insert.
   
       :rtype: list
       :return: List of documents IDs for the newly inserted documents.
       """
       logger.info('Inserting some documents in the {} table...'.format(table_name))
       statement = 'INSERT INTO {} ?'.format(table_name)
       cursor = transaction_executor.execute_statement(statement, convert_object_to_ion(documents))
       list_of_document_ids = get_document_ids_from_dml_results(cursor)
   
       return list_of_document_ids
   
   
   def update_and_insert_documents(transaction_executor):
       """
       Handle the insertion of documents and updating PersonIds all in a single transaction.
   
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
       """
       list_ids = insert_documents(transaction_executor, Constants.PERSON_TABLE_NAME, SampleData.PERSON)
   
       logger.info("Updating PersonIds for 'DriversLicense' and PrimaryOwner for 'VehicleRegistration'...")
       new_licenses, new_registrations = update_person_id(list_ids)
   
       insert_documents(transaction_executor, Constants.VEHICLE_TABLE_NAME, SampleData.VEHICLE)
       insert_documents(transaction_executor, Constants.VEHICLE_REGISTRATION_TABLE_NAME, new_registrations)
       insert_documents(transaction_executor, Constants.DRIVERS_LICENSE_TABLE_NAME, new_licenses)
   
   
   if __name__ == '__main__':
       """
       Insert documents into a table in a QLDB ledger.
       """
       try:
           with create_qldb_session() as session:
               # An INSERT statement creates the initial revision of a document with a version number of zero.
               # QLDB also assigns a unique document identifier in GUID format as part of the metadata.
               session.execute_lambda(lambda executor: update_and_insert_documents(executor),
                                      lambda retry_attempt: logger.info('Retrying due to OCC conflict...'))
               logger.info('Documents inserted successfully!')
       except Exception:
           logger.exception('Error inserting or updating documents.')
   ```

------
**catatan**  
Program ini menunjukkan cara memanggil `execute_statement` fungsi dengan nilai parameter. Anda dapat meneruskan parameter data selain pernyataan PartiQL yang ingin Anda jalankan. Gunakan tanda tanya (`?`) sebagai placeholder variabel dalam string pernyataan Anda.
Jika sebuah `INSERT` pernyataan berhasil, ia mengembalikan setiap dokumen `id` yang disisipkan.

1. Untuk menjalankan program, masukkan perintah berikut.

   ```
   python insert_document.py
   ```

Selanjutnya, Anda dapat menggunakan `SELECT` pernyataan untuk membaca data dari tabel di `vehicle-registration` buku besar. Lanjut ke [Langkah 4: Kueri tabel dalam buku besar](getting-started.python.step-4.md).

# Langkah 4: Kueri tabel dalam buku besar
<a name="getting-started.python.step-4"></a>

**penting**  
Pemberitahuan akhir dukungan: Pelanggan yang ada akan dapat menggunakan Amazon QLDB hingga akhir dukungan pada 07/31/2025. Untuk detail selengkapnya, lihat [Memigrasi Buku Besar QLDB Amazon ke Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

Setelah membuat tabel di buku besar QLDB Amazon dan memuatnya dengan data, Anda dapat menjalankan kueri untuk meninjau data registrasi kendaraan yang baru saja Anda masukkan. QLDB [menggunakan](ql-reference.md) PartiQL sebagai [bahasa kueri dan](ion.md) Amazon Ion sebagai model data berorientasi dokumen.

PartiQL adalah bahasa kueri open-source yang kompatibel dengan SQL yang telah diperluas untuk bekerja dengan Ion. Dengan PartiQL, Anda dapat menyisipkan, menanyakan, dan mengelola data Anda dengan operator SQL yang sudah dikenal. Amazon Ion adalah superset dari JSON. Ion adalah format data berbasis dokumen open-source yang memberi Anda fleksibilitas dalam menyimpan dan memproses data terstruktur, semi-terstruktur, dan bersarang.

Pada langkah ini, Anda menggunakan `SELECT` pernyataan untuk membaca data dari tabel di `vehicle-registration` buku besar.

**Awas**  
Saat Anda menjalankan kueri di QLDB tanpa pencarian yang diindeks, itu akan memanggil pemindaian tabel lengkap. PartiQL mendukung kueri seperti itu karena SQL kompatibel. Namun, *jangan* jalankan pemindaian tabel untuk kasus penggunaan produksi di QLDB. Pemindaian tabel dapat menyebabkan masalah kinerja pada tabel besar, termasuk konflik konkurensi dan batas waktu transaksi.  
Untuk menghindari pemindaian tabel, Anda harus menjalankan pernyataan dengan klausa `WHERE` predikat menggunakan operator *kesetaraan* pada bidang yang diindeks atau ID dokumen; misalnya, atau. `WHERE indexedField = 123` `WHERE indexedField IN (456, 789)` Untuk informasi selengkapnya, lihat [Mengoptimalkan kinerja kueri](working.optimize.md).

**Untuk menanyakan tabel**

1. Gunakan program berikut (`find_vehicles.py`) untuk menanyakan semua kendaraan yang terdaftar di bawah seseorang di buku besar Anda.

------
#### [ 3.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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from pyqldbsamples.model.sample_data import get_document_ids, print_result, SampleData
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.connect_to_ledger import create_qldb_driver
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def find_vehicles_for_owner(driver, gov_id):
       """
       Find vehicles registered under a driver using their government ID.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
   
       :type gov_id: str
       :param gov_id: The owner's government ID.
       """
       document_ids = driver.execute_lambda(lambda executor: get_document_ids(executor, Constants.PERSON_TABLE_NAME,
                                                                              'GovId', gov_id))
   
       query = "SELECT Vehicle FROM Vehicle INNER JOIN VehicleRegistration AS r " \
               "ON Vehicle.VIN = r.VIN WHERE r.Owners.PrimaryOwner.PersonId = ?"
   
       for ids in document_ids:
           cursor = driver.execute_lambda(lambda executor: executor.execute_statement(query, ids))
           logger.info('List of Vehicles for owner with GovId: {}...'.format(gov_id))
           print_result(cursor)
   
   
   def main(ledger_name=Constants.LEDGER_NAME):
       """
       Find all vehicles registered under a person.
       """
       try:
           with create_qldb_driver(ledger_name) as driver:
               # Find all vehicles registered under a person.
               gov_id = SampleData.PERSON[0]['GovId']
               find_vehicles_for_owner(driver, gov_id)
       except Exception as e:
           logger.exception('Error getting vehicles for owner.')
           raise e
   
   
   if __name__ == '__main__':
       main()
   ```

------
#### [ 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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from pyqldbsamples.model.sample_data import get_document_ids, print_result, SampleData
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.connect_to_ledger import create_qldb_session
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def find_vehicles_for_owner(transaction_executor, gov_id):
       """
       Find vehicles registered under a driver using their government ID.
   
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
   
       :type gov_id: str
       :param gov_id: The owner's government ID.
       """
       document_ids = get_document_ids(transaction_executor, Constants.PERSON_TABLE_NAME, 'GovId', gov_id)
   
       query = "SELECT Vehicle FROM Vehicle INNER JOIN VehicleRegistration AS r " \
               "ON Vehicle.VIN = r.VIN WHERE r.Owners.PrimaryOwner.PersonId = ?"
   
       for ids in document_ids:
           cursor = transaction_executor.execute_statement(query, ids)
           logger.info('List of Vehicles for owner with GovId: {}...'.format(gov_id))
           print_result(cursor)
   
   
   if __name__ == '__main__':
       """
       Find all vehicles registered under a person.
       """
       try:
           with create_qldb_session() as session:
               # Find all vehicles registered under a person.
               gov_id = SampleData.PERSON[0]['GovId']
               session.execute_lambda(lambda executor: find_vehicles_for_owner(executor, gov_id),
                                      lambda retry_attempt: logger.info('Retrying due to OCC conflict...'))
       except Exception:
           logger.exception('Error getting vehicles for owner.')
   ```

------
**catatan**  
Pertama, program ini menanyakan `Person` tabel untuk dokumen `GovId LEWISR261LL` untuk mendapatkan bidang `id` metadata-nya.  
Kemudian, ia menggunakan dokumen ini `id` sebagai kunci asing untuk menanyakan `VehicleRegistration` tabel dengan`PrimaryOwner.PersonId`. Itu juga bergabung `VehicleRegistration` dengan `Vehicle` meja di `VIN` lapangan.

1. Untuk menjalankan program, masukkan perintah berikut.

   ```
   python find_vehicles.py
   ```

Untuk mempelajari tentang memodifikasi dokumen dalam tabel di `vehicle-registration` buku besar, lihat. [Langkah 5: Ubah dokumen dalam buku besar](getting-started.python.step-5.md)

# Langkah 5: Ubah dokumen dalam buku besar
<a name="getting-started.python.step-5"></a>

**penting**  
Pemberitahuan akhir dukungan: Pelanggan yang ada akan dapat menggunakan Amazon QLDB hingga akhir dukungan pada 07/31/2025. Untuk detail selengkapnya, lihat [Memigrasi Buku Besar QLDB Amazon ke Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

Sekarang setelah Anda memiliki data untuk dikerjakan, Anda dapat mulai membuat perubahan pada dokumen di `vehicle-registration` buku besar di Amazon QLDB. Pada langkah ini, contoh kode berikut menunjukkan bagaimana menjalankan pernyataan bahasa manipulasi data (DHTML). Pernyataan ini memperbarui pemilik utama satu kendaraan dan menambahkan pemilik sekunder ke kendaraan lain.

**Untuk memodifikasi dokumen**

1. Gunakan program berikut (`transfer_vehicle_ownership.py`) untuk memperbarui pemilik utama kendaraan dengan VIN `1N4AL11D75C109151` di buku besar Anda.

------
#### [ 3.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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from pyqldbsamples.add_secondary_owner import get_document_ids, print_result, SampleData
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.model.sample_data import convert_object_to_ion
   from pyqldbsamples.connect_to_ledger import create_qldb_driver
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def find_person_from_document_id(transaction_executor, document_id):
       """
       Query a driver's information using the given ID.
   
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
   
       :type document_id: :py:class:`amazon.ion.simple_types.IonPyText`
       :param document_id: The document ID required to query for the person.
   
       :rtype: :py:class:`amazon.ion.simple_types.IonPyDict`
       :return: The resulting document from the query.
       """
       query = 'SELECT p.* FROM Person AS p BY pid WHERE pid = ?'
       cursor = transaction_executor.execute_statement(query, document_id)
       return next(cursor)
   
   
   def find_primary_owner_for_vehicle(driver, vin):
       """
       Find the primary owner of a vehicle given its VIN.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
   
       :type vin: str
       :param vin: The VIN to find primary owner for.
   
       :rtype: :py:class:`amazon.ion.simple_types.IonPyDict`
       :return: The resulting document from the query.
       """
       logger.info('Finding primary owner for vehicle with VIN: {}.'.format(vin))
       query = "SELECT Owners.PrimaryOwner.PersonId FROM VehicleRegistration AS v WHERE v.VIN = ?"
       cursor = driver.execute_lambda(lambda executor: executor.execute_statement(query, convert_object_to_ion(vin)))
       try:
           return driver.execute_lambda(lambda executor: find_person_from_document_id(executor,
                                                                                      next(cursor).get('PersonId')))
       except StopIteration:
           logger.error('No primary owner registered for this vehicle.')
           return None
   
   
   def update_vehicle_registration(driver, vin, document_id):
       """
       Update the primary owner for a vehicle using the given VIN.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
   
       :type vin: str
       :param vin: The VIN for the vehicle to operate on.
   
       :type document_id: :py:class:`amazon.ion.simple_types.IonPyText`
       :param document_id: New PersonId for the primary owner.
   
       :raises RuntimeError: If no vehicle registration was found using the given document ID and VIN.
       """
       logger.info('Updating the primary owner for vehicle with Vin: {}...'.format(vin))
       statement = "UPDATE VehicleRegistration AS r SET r.Owners.PrimaryOwner.PersonId = ? WHERE r.VIN = ?"
       cursor = driver.execute_lambda(lambda executor: executor.execute_statement(statement, document_id,
                                                                                  convert_object_to_ion(vin)))
       try:
           print_result(cursor)
           logger.info('Successfully transferred vehicle with VIN: {} to new owner.'.format(vin))
       except StopIteration:
           raise RuntimeError('Unable to transfer vehicle, could not find registration.')
   
   
   def validate_and_update_registration(driver, vin, current_owner, new_owner):
       """
       Validate the current owner of the given vehicle and transfer its ownership to a new owner.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
   
       :type vin: str
       :param vin: The VIN of the vehicle to transfer ownership of.
   
       :type current_owner: str
       :param current_owner: The GovId of the current owner of the vehicle.
   
       :type new_owner: str
       :param new_owner: The GovId of the new owner of the vehicle.
   
       :raises RuntimeError: If unable to verify primary owner.
       """
       primary_owner = find_primary_owner_for_vehicle(driver, vin)
       if primary_owner is None or primary_owner['GovId'] != current_owner:
           raise RuntimeError('Incorrect primary owner identified for vehicle, unable to transfer.')
   
       document_ids = driver.execute_lambda(lambda executor: get_document_ids(executor, Constants.PERSON_TABLE_NAME,
                                                                              'GovId', new_owner))
       update_vehicle_registration(driver, vin, document_ids[0])
   
   
   def main(ledger_name=Constants.LEDGER_NAME):
       """
       Find primary owner for a particular vehicle's VIN.
       Transfer to another primary owner for a particular vehicle's VIN.
       """
       vehicle_vin = SampleData.VEHICLE[0]['VIN']
       previous_owner = SampleData.PERSON[0]['GovId']
       new_owner = SampleData.PERSON[1]['GovId']
   
       try:
           with create_qldb_driver(ledger_name) as driver:
               validate_and_update_registration(driver, vehicle_vin, previous_owner, new_owner)
       except Exception as e:
           logger.exception('Error updating VehicleRegistration.')
           raise e
   
   
   if __name__ == '__main__':
       main()
   ```

------
#### [ 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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from pyqldbsamples.add_secondary_owner import get_document_ids, print_result, SampleData
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.model.sample_data import convert_object_to_ion
   from pyqldbsamples.connect_to_ledger import create_qldb_session
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def find_person_from_document_id(transaction_executor, document_id):
       """
       Query a driver's information using the given ID.
   
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
   
       :type document_id: :py:class:`amazon.ion.simple_types.IonPyText`
       :param document_id: The document ID required to query for the person.
   
       :rtype: :py:class:`amazon.ion.simple_types.IonPyDict`
       :return: The resulting document from the query.
       """
       query = 'SELECT p.* FROM Person AS p BY pid WHERE pid = ?'
       cursor = transaction_executor.execute_statement(query, document_id)
       return next(cursor)
   
   
   def find_primary_owner_for_vehicle(transaction_executor, vin):
       """
       Find the primary owner of a vehicle given its VIN.
   
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
   
       :type vin: str
       :param vin: The VIN to find primary owner for.
   
       :rtype: :py:class:`amazon.ion.simple_types.IonPyDict`
       :return: The resulting document from the query.
       """
       logger.info('Finding primary owner for vehicle with VIN: {}.'.format(vin))
       query = "SELECT Owners.PrimaryOwner.PersonId FROM VehicleRegistration AS v WHERE v.VIN = ?"
       cursor = transaction_executor.execute_statement(query, convert_object_to_ion(vin))
       try:
           return find_person_from_document_id(transaction_executor, next(cursor).get('PersonId'))
       except StopIteration:
           logger.error('No primary owner registered for this vehicle.')
           return None
   
   
   def update_vehicle_registration(transaction_executor, vin, document_id):
       """
       Update the primary owner for a vehicle using the given VIN.
   
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
   
       :type vin: str
       :param vin: The VIN for the vehicle to operate on.
   
       :type document_id: :py:class:`amazon.ion.simple_types.IonPyText`
       :param document_id: New PersonId for the primary owner.
   
       :raises RuntimeError: If no vehicle registration was found using the given document ID and VIN.
       """
       logger.info('Updating the primary owner for vehicle with Vin: {}...'.format(vin))
       statement = "UPDATE VehicleRegistration AS r SET r.Owners.PrimaryOwner.PersonId = ? WHERE r.VIN = ?"
       cursor = transaction_executor.execute_statement(statement, document_id, convert_object_to_ion(vin))
       try:
           print_result(cursor)
           logger.info('Successfully transferred vehicle with VIN: {} to new owner.'.format(vin))
       except StopIteration:
           raise RuntimeError('Unable to transfer vehicle, could not find registration.')
   
   
   def validate_and_update_registration(transaction_executor, vin, current_owner, new_owner):
       """
       Validate the current owner of the given vehicle and transfer its ownership to a new owner in a single transaction.
   
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
   
       :type vin: str
       :param vin: The VIN of the vehicle to transfer ownership of.
   
       :type current_owner: str
       :param current_owner: The GovId of the current owner of the vehicle.
   
       :type new_owner: str
       :param new_owner: The GovId of the new owner of the vehicle.
   
       :raises RuntimeError: If unable to verify primary owner.
       """
       primary_owner = find_primary_owner_for_vehicle(transaction_executor, vin)
       if primary_owner is None or primary_owner['GovId'] != current_owner:
           raise RuntimeError('Incorrect primary owner identified for vehicle, unable to transfer.')
   
       document_id = next(get_document_ids(transaction_executor, Constants.PERSON_TABLE_NAME, 'GovId', new_owner))
   
       update_vehicle_registration(transaction_executor, vin, document_id)
   
   
   if __name__ == '__main__':
       """
       Find primary owner for a particular vehicle's VIN.
       Transfer to another primary owner for a particular vehicle's VIN.
       """
       vehicle_vin = SampleData.VEHICLE[0]['VIN']
       previous_owner = SampleData.PERSON[0]['GovId']
       new_owner = SampleData.PERSON[1]['GovId']
   
       try:
           with create_qldb_session() as session:
               session.execute_lambda(lambda executor: validate_and_update_registration(executor, vehicle_vin,
                                                                                        previous_owner, new_owner),
                                      retry_indicator=lambda retry_attempt: logger.info('Retrying due to OCC conflict...'))
       except Exception:
           logger.exception('Error updating VehicleRegistration.')
   ```

------

1. Untuk menjalankan program, masukkan perintah berikut.

   ```
   python transfer_vehicle_ownership.py
   ```

1. Gunakan program berikut (`add_secondary_owner.py`) untuk menambahkan pemilik sekunder ke kendaraan dengan VIN `KM8SRDHF6EU074761` di buku besar Anda.

------
#### [ 3.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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from pyqldbsamples.model.sample_data import to_ion_struct, get_document_ids, print_result, SampleData, \
       convert_object_to_ion
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.connect_to_ledger import create_qldb_driver
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def get_document_id_by_gov_id(driver, government_id):
       """
       Find a driver's person ID using the given government ID.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
   
       :type government_id: str
       :param government_id: A driver's government ID.
   
       :rtype: list
       :return: A list of document IDs.
       """
       logger.info("Finding secondary owner's person ID using given government ID: {}.".format(government_id))
       return driver.execute_lambda(lambda executor: get_document_ids(executor, Constants.PERSON_TABLE_NAME, 'GovId',
                                                                      government_id))
   
   
   def is_secondary_owner_for_vehicle(driver, vin, secondary_owner_id):
       """
       Check whether a secondary owner has already been registered for the given VIN.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
   
       :type vin: str
       :param vin: VIN of the vehicle to query.
   
       :type secondary_owner_id: str
       :param secondary_owner_id: The secondary owner's person ID.
   
       :rtype: bool
       :return: If the driver has already been registered.
       """
       logger.info('Finding secondary owners for vehicle with VIN: {}...'.format(vin))
       query = 'SELECT Owners.SecondaryOwners FROM VehicleRegistration AS v WHERE v.VIN = ?'
       rows = driver.execute_lambda(lambda executor: executor.execute_statement(query, convert_object_to_ion(vin)))
   
       for row in rows:
           secondary_owners = row.get('SecondaryOwners')
           person_ids = map(lambda owner: owner.get('PersonId').text, secondary_owners)
           if secondary_owner_id in person_ids:
               return True
       return False
   
   
   def add_secondary_owner_for_vin(driver, vin, parameter):
       """
       Add a secondary owner into `VehicleRegistration` table for a particular VIN.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
   
       :type vin: str
       :param vin: VIN of the vehicle to add a secondary owner for.
   
       :type parameter: :py:class:`amazon.ion.simple_types.IonPyValue`
       :param parameter: The Ion value or Python native type that is convertible to Ion for filling in parameters of the
                         statement.
       """
       logger.info('Inserting secondary owner for vehicle with VIN: {}...'.format(vin))
       statement = "FROM VehicleRegistration AS v WHERE v.VIN = ? INSERT INTO v.Owners.SecondaryOwners VALUE ?"
   
       cursor = driver.execute_lambda(lambda executor: executor.execute_statement(statement, convert_object_to_ion(vin),
                                                                                  parameter))
       logger.info('VehicleRegistration Document IDs which had secondary owners added: ')
       print_result(cursor)
   
   
   def register_secondary_owner(driver, vin, gov_id):
       """
       Register a secondary owner for a vehicle if they are not already registered.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
   
       :type vin: str
       :param vin: VIN of the vehicle to register a secondary owner for.
   
       :type gov_id: str
       :param gov_id: The government ID of the owner.
       """
       logger.info('Finding the secondary owners for vehicle with VIN: {}.'.format(vin))
   
       document_ids = get_document_id_by_gov_id(driver, gov_id)
   
       for document_id in document_ids:
           if is_secondary_owner_for_vehicle(driver, vin, document_id):
               logger.info('Person with ID {} has already been added as a secondary owner of this vehicle.'.format(gov_id))
           else:
               add_secondary_owner_for_vin(driver, vin, to_ion_struct('PersonId', document_id))
   
   
   def main(ledger_name=Constants.LEDGER_NAME):
       """
       Finds and adds secondary owners for a vehicle.
       """
       vin = SampleData.VEHICLE[1]['VIN']
       gov_id = SampleData.PERSON[0]['GovId']
       try:
           with create_qldb_driver(ledger_name) as driver:
               register_secondary_owner(driver, vin, gov_id)
               logger.info('Secondary owners successfully updated.')
       except Exception as e:
           logger.exception('Error adding secondary owner.')
           raise e
   
   
   if __name__ == '__main__':
       main()
   ```

------
#### [ 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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from pyqldbsamples.model.sample_data import to_ion_struct, get_document_ids, print_result, SampleData, \
       convert_object_to_ion
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.connect_to_ledger import create_qldb_session
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def get_document_id_by_gov_id(transaction_executor, government_id):
       """
       Find a driver's person ID using the given government ID.
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
       :type government_id: str
       :param government_id: A driver's government ID.
       :rtype: list
       :return: A list of document IDs.
       """
       logger.info("Finding secondary owner's person ID using given government ID: {}.".format(government_id))
       return get_document_ids(transaction_executor, Constants.PERSON_TABLE_NAME, 'GovId', government_id)
   
   
   def is_secondary_owner_for_vehicle(transaction_executor, vin, secondary_owner_id):
       """
       Check whether a secondary owner has already been registered for the given VIN.
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
       :type vin: str
       :param vin: VIN of the vehicle to query.
       :type secondary_owner_id: str
       :param secondary_owner_id: The secondary owner's person ID.
       :rtype: bool
       :return: If the driver has already been registered.
       """
       logger.info('Finding secondary owners for vehicle with VIN: {}...'.format(vin))
       query = 'SELECT Owners.SecondaryOwners FROM VehicleRegistration AS v WHERE v.VIN = ?'
       rows = transaction_executor.execute_statement(query, convert_object_to_ion(vin))
   
       for row in rows:
           secondary_owners = row.get('SecondaryOwners')
           person_ids = map(lambda owner: owner.get('PersonId').text, secondary_owners)
           if secondary_owner_id in person_ids:
               return True
       return False
   
   
   def add_secondary_owner_for_vin(transaction_executor, vin, parameter):
       """
       Add a secondary owner into `VehicleRegistration` table for a particular VIN.
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
       :type vin: str
       :param vin: VIN of the vehicle to add a secondary owner for.
       :type parameter: :py:class:`amazon.ion.simple_types.IonPyValue`
       :param parameter: The Ion value or Python native type that is convertible to Ion for filling in parameters of the
                         statement.
       """
       logger.info('Inserting secondary owner for vehicle with VIN: {}...'.format(vin))
       statement = "FROM VehicleRegistration AS v WHERE v.VIN = '{}' INSERT INTO v.Owners.SecondaryOwners VALUE ?"\
           .format(vin)
   
       cursor = transaction_executor.execute_statement(statement, parameter)
       logger.info('VehicleRegistration Document IDs which had secondary owners added: ')
       print_result(cursor)
   
   
   def register_secondary_owner(transaction_executor, vin, gov_id):
       """
       Register a secondary owner for a vehicle if they are not already registered.
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
       :type vin: str
       :param vin: VIN of the vehicle to register a secondary owner for.
       :type gov_id: str
       :param gov_id: The government ID of the owner.
       """
       logger.info('Finding the secondary owners for vehicle with VIN: {}.'.format(vin))
       document_ids = get_document_id_by_gov_id(transaction_executor, gov_id)
   
       for document_id in document_ids:
           if is_secondary_owner_for_vehicle(transaction_executor, vin, document_id):
               logger.info('Person with ID {} has already been added as a secondary owner of this vehicle.'.format(gov_id))
           else:
               add_secondary_owner_for_vin(transaction_executor, vin, to_ion_struct('PersonId', document_id))
   
   
   if __name__ == '__main__':
       """
       Finds and adds secondary owners for a vehicle.
       """
       vin = SampleData.VEHICLE[1]['VIN']
       gov_id = SampleData.PERSON[0]['GovId']
       try:
           with create_qldb_session() as session:
               session.execute_lambda(lambda executor: register_secondary_owner(executor, vin, gov_id),
                                      lambda retry_attempt: logger.info('Retrying due to OCC conflict...'))
               logger.info('Secondary owners successfully updated.')
       except Exception:
           logger.exception('Error adding secondary owner.')
   ```

------

1. Untuk menjalankan program, masukkan perintah berikut.

   ```
   python add_secondary_owner.py
   ```

Untuk meninjau perubahan ini di `vehicle-registration` buku besar, lihat[Langkah 6: Lihat riwayat revisi untuk dokumen](getting-started.python.step-6.md).

# Langkah 6: Lihat riwayat revisi untuk dokumen
<a name="getting-started.python.step-6"></a>

**penting**  
Pemberitahuan akhir dukungan: Pelanggan yang ada akan dapat menggunakan Amazon QLDB hingga akhir dukungan pada 07/31/2025. Untuk detail selengkapnya, lihat [Memigrasi Buku Besar QLDB Amazon ke Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

Setelah memodifikasi data registrasi untuk kendaraan pada langkah sebelumnya, Anda dapat menanyakan riwayat semua pemilik terdaftar dan bidang yang diperbarui lainnya. Pada langkah ini, Anda menanyakan riwayat revisi dokumen dalam `VehicleRegistration` tabel di `vehicle-registration` buku besar Anda.

**Untuk melihat riwayat revisi**

1. Gunakan program berikut (`query_history.py`) untuk menanyakan riwayat revisi `VehicleRegistration` dokumen dengan VIN`1N4AL11D75C109151`.

------
#### [ 3.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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from datetime import datetime, timedelta
   from logging import basicConfig, getLogger, INFO
   
   from pyqldbsamples.model.sample_data import print_result, get_document_ids, SampleData
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.connect_to_ledger import create_qldb_driver
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def format_date_time(date_time):
       """
       Format the given date time to a string.
   
       :type date_time: :py:class:`datetime.datetime`
       :param date_time: The date time to format.
   
       :rtype: str
       :return: The formatted date time.
       """
       return date_time.strftime('`%Y-%m-%dT%H:%M:%S.%fZ`')
   
   
   def previous_primary_owners(driver, vin):
       """
       Find previous primary owners for the given VIN in a single transaction.
       In this example, query the `VehicleRegistration` history table to find all previous primary owners for a VIN.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
   
       :type vin: str
       :param vin: VIN to find previous primary owners for.
       """
       person_ids = driver.execute_lambda(lambda executor: get_document_ids(executor,
                                                                            Constants.VEHICLE_REGISTRATION_TABLE_NAME,
                                                                            'VIN', vin))
   
       todays_date = datetime.utcnow() - timedelta(seconds=1)
       three_months_ago = todays_date - timedelta(days=90)
       query = 'SELECT data.Owners.PrimaryOwner, metadata.version FROM history({}, {}, {}) AS h WHERE h.metadata.id = ?'.\
           format(Constants.VEHICLE_REGISTRATION_TABLE_NAME, format_date_time(three_months_ago),
                  format_date_time(todays_date))
   
       for ids in person_ids:
           logger.info("Querying the 'VehicleRegistration' table's history using VIN: {}.".format(vin))
           cursor = driver.execute_lambda(lambda executor: executor.execute_statement(query, ids))
           if not (print_result(cursor)) > 0:
               logger.info('No modification history found within the given time frame for document ID: {}'.format(ids))
   
   
   def main(ledger_name=Constants.LEDGER_NAME):
       """
       Query a table's history for a particular set of documents.
       """
       try:
           with create_qldb_driver(ledger_name) as driver:
               vin = SampleData.VEHICLE_REGISTRATION[0]['VIN']
               previous_primary_owners(driver, vin)
               logger.info('Successfully queried history.')
       except Exception as e:
           logger.exception('Unable to query history to find previous owners.')
           raise e
   
   
   if __name__ == '__main__':
       main()
   ```

------
#### [ 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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from datetime import datetime, timedelta
   from logging import basicConfig, getLogger, INFO
   
   from pyqldbsamples.model.sample_data import print_result, get_document_ids, SampleData
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.connect_to_ledger import create_qldb_session
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   
   
   def format_date_time(date_time):
       """
       Format the given date time to a string.
   
       :type date_time: :py:class:`datetime.datetime`
       :param date_time: The date time to format.
   
       :rtype: str
       :return: The formatted date time.
       """
       return date_time.strftime('`%Y-%m-%dT%H:%M:%S.%fZ`')
   
   
   def previous_primary_owners(transaction_executor, vin):
       """
       Find previous primary owners for the given VIN in a single transaction.
       In this example, query the `VehicleRegistration` history table to find all previous primary owners for a VIN.
   
       :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor`
       :param transaction_executor: An Executor object allowing for execution of statements within a transaction.
   
       :type vin: str
       :param vin: VIN to find previous primary owners for.
       """
       person_ids = get_document_ids(transaction_executor, Constants.VEHICLE_REGISTRATION_TABLE_NAME, 'VIN', vin)
   
       todays_date = datetime.utcnow() - timedelta(seconds=1)
       three_months_ago = todays_date - timedelta(days=90)
       query = 'SELECT data.Owners.PrimaryOwner, metadata.version FROM history({}, {}, {}) AS h WHERE h.metadata.id = ?'.\
           format(Constants.VEHICLE_REGISTRATION_TABLE_NAME, format_date_time(three_months_ago),
                  format_date_time(todays_date))
   
       for ids in person_ids:
           logger.info("Querying the 'VehicleRegistration' table's history using VIN: {}.".format(vin))
           cursor = transaction_executor.execute_statement(query, ids)
           if not (print_result(cursor)) > 0:
               logger.info('No modification history found within the given time frame for document ID: {}'.format(ids))
   
   
   if __name__ == '__main__':
       """
       Query a table's history for a particular set of documents.
       """
       try:
           with create_qldb_session() as session:
               vin = SampleData.VEHICLE_REGISTRATION[0]['VIN']
               session.execute_lambda(lambda lambda_executor: previous_primary_owners(lambda_executor, vin),
                                      lambda retry_attempt: logger.info('Retrying due to OCC conflict...'))
               logger.info('Successfully queried history.')
       except Exception:
           logger.exception('Unable to query history to find previous owners.')
   ```

------
**catatan**  
Anda dapat melihat riwayat revisi dokumen dengan menanyakan built-in [Fungsi sejarah](working.history.md#working.history.function) dalam sintaks berikut.  

     ```
     SELECT * FROM history( table_name [, `start-time` [, `end-time` ] ] ) AS h
     [ WHERE h.metadata.id = 'id' ]
     ```
Waktu *mulai dan akhir waktu* *keduanya opsional*. Itu adalah nilai literal Amazon Ion yang dapat dilambangkan dengan backticks (). ``...`` Untuk mempelajari selengkapnya, lihat [Meminta Ion dengan PartiQL di Amazon QLDB](ql-reference.query.md).
Sebagai praktik terbaik, kualifikasikan kueri riwayat dengan rentang tanggal (waktu mulai *dan *akhir waktu**) dan ID dokumen (). `metadata.id` [QLDB `SELECT` memproses kueri dalam transaksi, yang tunduk pada batas waktu tunggu transaksi.](limits.md#limits.fixed)  
Riwayat QLDB diindeks oleh ID dokumen, dan Anda tidak dapat membuat indeks riwayat tambahan saat ini. Pertanyaan sejarah yang mencakup waktu mulai dan waktu akhir mendapatkan manfaat dari kualifikasi rentang tanggal.

1. Untuk menjalankan program, masukkan perintah berikut.

   ```
   python query_history.py
   ```

Untuk memverifikasi revisi dokumen secara kriptografis di `vehicle-registration` buku besar, lanjutkan ke. [Langkah 7: Verifikasi dokumen dalam buku besar](getting-started.python.step-7.md)

# Langkah 7: Verifikasi dokumen dalam buku besar
<a name="getting-started.python.step-7"></a>

**penting**  
Pemberitahuan akhir dukungan: Pelanggan yang ada akan dapat menggunakan Amazon QLDB hingga akhir dukungan pada 07/31/2025. Untuk detail selengkapnya, lihat [Memigrasi Buku Besar QLDB Amazon ke Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

Dengan Amazon QLDB, Anda dapat memverifikasi integritas dokumen secara efisien dalam jurnal buku besar Anda dengan menggunakan hashing kriptografi dengan SHA-256. Untuk mempelajari lebih lanjut tentang cara kerja verifikasi dan hashing kriptografi di QLDB, lihat. [Verifikasi data di Amazon QLDB](verification.md)

Pada langkah ini, Anda memverifikasi revisi dokumen dalam `VehicleRegistration` tabel di `vehicle-registration` buku besar Anda. Pertama, Anda meminta intisari, yang dikembalikan sebagai file output dan bertindak sebagai tanda tangan dari seluruh riwayat perubahan buku besar Anda. Kemudian, Anda meminta bukti untuk revisi relatif terhadap intisari itu. Dengan menggunakan bukti ini, integritas revisi Anda diverifikasi jika semua pemeriksaan validasi lulus.

**Untuk memverifikasi revisi dokumen**

1. Tinjau `.py` file berikut, yang mewakili objek QLDB yang diperlukan untuk verifikasi dan modul utilitas dengan fungsi pembantu untuk mengonversi jenis respons QLDB menjadi string.

   1. `block_address.py`

      ```
      # 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.
      
      
      def block_address_to_dictionary(ion_dict):
          """
          Convert a block address from IonPyDict into a dictionary.
          Shape of the dictionary must be: {'IonText': "{strandId: <"strandId">, sequenceNo: <sequenceNo>}"}
      
          :type ion_dict: :py:class:`amazon.ion.simple_types.IonPyDict`/str
          :param ion_dict: The block address value to convert.
      
          :rtype: dict
          :return: The converted dict.
          """
          block_address = {'IonText': {}}
          if not isinstance(ion_dict, str):
              py_dict = '{{strandId: "{}", sequenceNo:{}}}'.format(ion_dict['strandId'], ion_dict['sequenceNo'])
              ion_dict = py_dict
          block_address['IonText'] = ion_dict
          return block_address
      ```

   1. `verifier.py`

      ```
      # 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.
      #
      # This code expects that you have AWS credentials setup per:
      # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
      from array import array
      from base64 import b64encode
      from functools import reduce
      from hashlib import sha256
      from random import randrange
      
      from amazon.ion.simpleion import loads
      
      HASH_LENGTH = 32
      UPPER_BOUND = 8
      
      
      def parse_proof(value_holder):
          """
          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>}}]'}
      
          :type value_holder: dict
          :param value_holder: A structure containing an Ion string value.
      
          :rtype: :py:class:`amazon.ion.simple_types.IonPyList`
          :return: A list of hash values.
          """
          value_holder = value_holder.get('IonText')
          proof_list = loads(value_holder)
          return proof_list
      
      
      def parse_block(value_holder):
          """
          Parse the Block object returned by QLDB and retrieve block hash.
      
          :type value_holder: dict
          :param value_holder: A structure containing an Ion string value.
      
          :rtype: :py:class:`amazon.ion.simple_types.IonPyBytes`
          :return: The block hash.
          """
          value_holder = value_holder.get('IonText')
          block = loads(value_holder)
          block_hash = block.get('blockHash')
          return block_hash
      
      
      def flip_random_bit(original):
          """
          Flip a single random bit in the given hash value.
          This method is used to demonstrate QLDB's verification features.
      
          :type original: bytes
          :param original: The hash value to alter.
      
          :rtype: bytes
          :return: The altered hash with a single random bit changed.
          """
          assert len(original) != 0, 'Invalid bytes.'
      
          altered_position = randrange(len(original))
          bit_shift = randrange(UPPER_BOUND)
          altered_hash = bytearray(original).copy()
      
          altered_hash[altered_position] = altered_hash[altered_position] ^ (1 << bit_shift)
          return bytes(altered_hash)
      
      
      def compare_hash_values(hash1, hash2):
          """
          Compare two hash values by converting them into byte arrays, assuming they are little endian.
      
          :type hash1: bytes
          :param hash1: The hash value to compare.
      
          :type hash2: bytes
          :param hash2: The hash value to compare.
      
          :rtype: int
          :return: Zero if the hash values are equal, otherwise return the difference of the first pair of non-matching bytes.
          """
          assert len(hash1) == HASH_LENGTH
          assert len(hash2) == HASH_LENGTH
      
          hash_array1 = array('b', hash1)
          hash_array2 = array('b', hash2)
      
          for i in range(len(hash_array1) - 1, -1, -1):
              difference = hash_array1[i] - hash_array2[i]
              if difference != 0:
                  return difference
          return 0
      
      
      def join_hash_pairwise(hash1, hash2):
          """
          Take two hash values, sort them, concatenate them, and generate a new hash value from the concatenated values.
      
          :type hash1: bytes
          :param hash1: Hash value to concatenate.
      
          :type hash2: bytes
          :param hash2: Hash value to concatenate.
      
          :rtype: bytes
          :return: The new hash value generated from concatenated hash values.
          """
          if len(hash1) == 0:
              return hash2
          if len(hash2) == 0:
              return hash1
      
          concatenated = hash1 + hash2 if compare_hash_values(hash1, hash2) < 0 else hash2 + hash1
          new_hash_lib = sha256()
          new_hash_lib.update(concatenated)
          new_digest = new_hash_lib.digest()
          return new_digest
      
      
      def calculate_root_hash_from_internal_hashes(internal_hashes, leaf_hash):
          """
          Combine the internal hashes and the leaf hash until only one root hash remains.
      
          :type internal_hashes: map
          :param internal_hashes: An iterable over a list of hash values.
      
          :type leaf_hash: bytes
          :param leaf_hash: The revision hash to pair with the first hash in the Proof hashes list.
      
          :rtype: bytes
          :return: The root hash constructed by combining internal hashes.
          """
          root_hash = reduce(join_hash_pairwise, internal_hashes, leaf_hash)
          return root_hash
      
      
      def build_candidate_digest(proof, leaf_hash):
          """
          Build the candidate digest representing the entire ledger from the Proof hashes.
      
          :type proof: dict
          :param proof: The Proof object.
      
          :type leaf_hash: bytes
          :param leaf_hash: The revision hash to pair with the first hash in the Proof hashes list.
      
          :rtype: bytes
          :return: The calculated root hash.
          """
          parsed_proof = parse_proof(proof)
          root_hash = calculate_root_hash_from_internal_hashes(parsed_proof, leaf_hash)
          return root_hash
      
      
      def verify_document(document_hash, digest, proof):
          """
          Verify document revision against the provided digest.
      
          :type document_hash: bytes
          :param document_hash: The SHA-256 value representing the document revision to be verified.
      
          :type digest: bytes
          :param digest: The SHA-256 hash value representing the ledger digest.
      
          :type proof: dict
          :param proof: The Proof object retrieved from :func:`pyqldbsamples.get_revision.get_revision`.
      
          :rtype: bool
          :return: If the document revision verify against the ledger digest.
          """
          candidate_digest = build_candidate_digest(proof, document_hash)
          return digest == candidate_digest
      
      
      def to_base_64(input):
          """
          Encode input in base64.
      
          :type input: bytes
          :param input: Input to be encoded.
      
          :rtype: string
          :return: Return input that has been encoded in base64.
          """
          encoded_value = b64encode(input)
          return str(encoded_value, 'UTF-8')
      ```

   1. `qldb_string_utils.py`

      ```
      # 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.
      from amazon.ion.simpleion import dumps, loads
      
      
      def value_holder_to_string(value_holder):
          """
          Returns the string representation of a given `value_holder`.
      
          :type value_holder: dict
          :param value_holder: The `value_holder` to convert to string.
      
          :rtype: str
          :return: The string representation of the supplied `value_holder`.
          """
          ret_val = dumps(loads(value_holder), binary=False, indent='  ', omit_version_marker=True)
          val = '{{ IonText: {}}}'.format(ret_val)
          return val
      
      
      def block_response_to_string(block_response):
          """
          Returns the string representation of a given `block_response`.
      
          :type block_response: dict
          :param block_response: The `block_response` to convert to string.
      
          :rtype: str
          :return: The string representation of the supplied `block_response`.
          """
          string = ''
          if block_response.get('Block', {}).get('IonText') is not None:
              string += 'Block: ' + value_holder_to_string(block_response['Block']['IonText']) + ', '
      
          if block_response.get('Proof', {}).get('IonText') is not None:
              string += 'Proof: ' + value_holder_to_string(block_response['Proof']['IonText'])
      
          return '{' + string + '}'
      
      
      def digest_response_to_string(digest_response):
          """
          Returns the string representation of a given `digest_response`.
      
          :type digest_response: dict
          :param digest_response: The `digest_response` to convert to string.
      
          :rtype: str
          :return: The string representation of the supplied `digest_response`.
          """
          string = ''
          if digest_response.get('Digest') is not None:
              string += 'Digest: ' + str(digest_response['Digest']) + ', '
      
          if digest_response.get('DigestTipAddress', {}).get('IonText') is not None:
              string += 'DigestTipAddress: ' + value_holder_to_string(digest_response['DigestTipAddress']['IonText'])
      
          return '{' + string + '}'
      ```

1. Gunakan dua `.py` program (`get_digest.py`dan`get_revision.py`) untuk melakukan langkah-langkah berikut:
   + Minta intisari baru dari `vehicle-registration` buku besar.
   + Minta bukti untuk setiap revisi dokumen dengan VIN `1N4AL11D75C109151` dari `VehicleRegistration` tabel.
   + Verifikasi revisi menggunakan intisari dan bukti yang dikembalikan dengan menghitung ulang intisari.

   `get_digest.py`Program ini berisi kode berikut.

   ```
   # 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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from boto3 import client
   
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.qldb.qldb_string_utils import digest_response_to_string
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   qldb_client = client('qldb')
   
   
   def get_digest_result(name):
       """
       Get the digest of a ledger's journal.
   
       :type name: str
       :param name: Name of the ledger to operate on.
   
       :rtype: dict
       :return: The digest in a 256-bit hash value and a block address.
       """
       logger.info("Let's get the current digest of the ledger named {}".format(name))
       result = qldb_client.get_digest(Name=name)
       logger.info('Success. LedgerDigest: {}.'.format(digest_response_to_string(result)))
       return result
   
   
   def main(ledger_name=Constants.LEDGER_NAME):
       """
       This is an example for retrieving the digest of a particular ledger.
       """
       try:
           get_digest_result(ledger_name)
       except Exception as e:
           logger.exception('Unable to get a ledger digest!')
           raise e
   
   
   if __name__ == '__main__':
       main()
   ```
**catatan**  
Gunakan `get_digest_result` fungsi untuk meminta intisari yang mencakup *ujung* jurnal saat ini di buku besar Anda. Tip jurnal mengacu pada blok komitmen terbaru pada saat QLDB menerima permintaan Anda.

   `get_revision.py`Program ini berisi kode berikut.

------
#### [ 3.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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from amazon.ion.simpleion import loads
   from boto3 import client
   
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.get_digest import get_digest_result
   from pyqldbsamples.model.sample_data import SampleData, convert_object_to_ion
   from pyqldbsamples.qldb.block_address import block_address_to_dictionary
   from pyqldbsamples.verifier import verify_document, flip_random_bit, to_base_64
   from pyqldbsamples.connect_to_ledger import create_qldb_driver
   from pyqldbsamples.qldb.qldb_string_utils import value_holder_to_string
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   qldb_client = client('qldb')
   
   
   def get_revision(ledger_name, document_id, block_address, digest_tip_address):
       """
       Get the revision data object for a specified document ID and block address.
       Also returns a proof of the specified revision for verification.
   
       :type ledger_name: str
       :param ledger_name: Name of the ledger containing the document to query.
   
       :type document_id: str
       :param document_id: Unique ID for the document to be verified, contained in the committed view of the document.
   
       :type block_address: dict
       :param block_address: The location of the block to request.
   
       :type digest_tip_address: dict
       :param digest_tip_address: The latest block location covered by the digest.
   
       :rtype: dict
       :return: The response of the request.
       """
       result = qldb_client.get_revision(Name=ledger_name, BlockAddress=block_address, DocumentId=document_id,
                                         DigestTipAddress=digest_tip_address)
       return result
   
   
   def lookup_registration_for_vin(driver, vin):
       """
       Query revision history for a particular vehicle for verification.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
   
       :type vin: str
       :param vin: VIN to query the revision history of a specific registration with.
   
       :rtype: :py:class:`pyqldb.cursor.buffered_cursor.BufferedCursor`
       :return: Cursor on the result set of the statement query.
       """
       logger.info("Querying the 'VehicleRegistration' table for VIN: {}...".format(vin))
       query = 'SELECT * FROM _ql_committed_VehicleRegistration WHERE data.VIN = ?'
       return driver.execute_lambda(lambda txn: txn.execute_statement(query, convert_object_to_ion(vin)))
   
   
   def verify_registration(driver, ledger_name, vin):
       """
       Verify each version of the registration for the given VIN.
   
       :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver`
       :param driver: An instance of the QldbDriver class.
   
       :type ledger_name: str
       :param ledger_name: The ledger to get digest from.
   
       :type vin: str
       :param vin: VIN to query the revision history of a specific registration with.
   
       :raises AssertionError: When verification failed.
       """
       logger.info("Let's verify the registration with VIN = {}, in ledger = {}.".format(vin, ledger_name))
       digest = get_digest_result(ledger_name)
       digest_bytes = digest.get('Digest')
       digest_tip_address = digest.get('DigestTipAddress')
   
       logger.info('Got a ledger digest: digest tip address = {}, digest = {}.'.format(
           value_holder_to_string(digest_tip_address.get('IonText')), to_base_64(digest_bytes)))
   
       logger.info('Querying the registration with VIN = {} to verify each version of the registration...'.format(vin))
       cursor = lookup_registration_for_vin(driver, vin)
       logger.info('Getting a proof for the document.')
   
       for row in cursor:
           block_address = row.get('blockAddress')
           document_id = row.get('metadata').get('id')
   
           result = get_revision(ledger_name, document_id, block_address_to_dictionary(block_address), digest_tip_address)
           revision = result.get('Revision').get('IonText')
           document_hash = loads(revision).get('hash')
   
           proof = result.get('Proof')
           logger.info('Got back a proof: {}.'.format(proof))
   
           verified = verify_document(document_hash, digest_bytes, proof)
           if not verified:
               raise AssertionError('Document revision is not verified.')
           else:
               logger.info('Success! The document is verified.')
   
           altered_document_hash = flip_random_bit(document_hash)
           logger.info("Flipping one bit in the document's hash and assert that the document is NOT verified. "
                       "The altered document hash is: {}.".format(to_base_64(altered_document_hash)))
           verified = verify_document(altered_document_hash, digest_bytes, proof)
           if verified:
               raise AssertionError('Expected altered document hash to not be verified against digest.')
           else:
               logger.info('Success! As expected flipping a bit in the document hash causes verification to fail.')
   
           logger.info('Finished verifying the registration with VIN = {} in ledger = {}.'.format(vin, ledger_name))
   
   
   def main(ledger_name=Constants.LEDGER_NAME):
       """
       Verify the integrity of a document revision in a QLDB ledger.
       """
       registration = SampleData.VEHICLE_REGISTRATION[0]
       vin = registration['VIN']
       try:
           with create_qldb_driver(ledger_name) as driver:
               verify_registration(driver, ledger_name, vin)
       except Exception as e:
           logger.exception('Unable to verify revision.')
           raise e
   
   
   if __name__ == '__main__':
       main()
   ```

------
#### [ 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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from amazon.ion.simpleion import loads
   from boto3 import client
   
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.get_digest import get_digest_result
   from pyqldbsamples.model.sample_data import SampleData, convert_object_to_ion
   from pyqldbsamples.qldb.block_address import block_address_to_dictionary
   from pyqldbsamples.verifier import verify_document, flip_random_bit, to_base_64
   from pyqldbsamples.connect_to_ledger import create_qldb_session
   from pyqldbsamples.qldb.qldb_string_utils import value_holder_to_string
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   qldb_client = client('qldb')
   
   
   def get_revision(ledger_name, document_id, block_address, digest_tip_address):
       """
       Get the revision data object for a specified document ID and block address.
       Also returns a proof of the specified revision for verification.
   
       :type ledger_name: str
       :param ledger_name: Name of the ledger containing the document to query.
   
       :type document_id: str
       :param document_id: Unique ID for the document to be verified, contained in the committed view of the document.
   
       :type block_address: dict
       :param block_address: The location of the block to request.
   
       :type digest_tip_address: dict
       :param digest_tip_address: The latest block location covered by the digest.
   
       :rtype: dict
       :return: The response of the request.
       """
       result = qldb_client.get_revision(Name=ledger_name, BlockAddress=block_address, DocumentId=document_id,
                                         DigestTipAddress=digest_tip_address)
       return result
   
   
   def lookup_registration_for_vin(qldb_session, vin):
       """
       Query revision history for a particular vehicle for verification.
   
       :type qldb_session: :py:class:`pyqldb.session.qldb_session.QldbSession`
       :param qldb_session: An instance of the QldbSession class.
   
       :type vin: str
       :param vin: VIN to query the revision history of a specific registration with.
   
       :rtype: :py:class:`pyqldb.cursor.buffered_cursor.BufferedCursor`
       :return: Cursor on the result set of the statement query.
       """
       logger.info("Querying the 'VehicleRegistration' table for VIN: {}...".format(vin))
       query = 'SELECT * FROM _ql_committed_VehicleRegistration WHERE data.VIN = ?'
       parameters = [convert_object_to_ion(vin)]
       cursor = qldb_session.execute_statement(query, parameters)
       return cursor
   
   
   def verify_registration(qldb_session, ledger_name, vin):
       """
       Verify each version of the registration for the given VIN.
   
       :type qldb_session: :py:class:`pyqldb.session.qldb_session.QldbSession`
       :param qldb_session: An instance of the QldbSession class.
   
       :type ledger_name: str
       :param ledger_name: The ledger to get digest from.
   
       :type vin: str
       :param vin: VIN to query the revision history of a specific registration with.
   
       :raises AssertionError: When verification failed.
       """
       logger.info("Let's verify the registration with VIN = {}, in ledger = {}.".format(vin, ledger_name))
       digest = get_digest_result(ledger_name)
       digest_bytes = digest.get('Digest')
       digest_tip_address = digest.get('DigestTipAddress')
   
       logger.info('Got a ledger digest: digest tip address = {}, digest = {}.'.format(
           value_holder_to_string(digest_tip_address.get('IonText')), to_base_64(digest_bytes)))
   
       logger.info('Querying the registration with VIN = {} to verify each version of the registration...'.format(vin))
       cursor = lookup_registration_for_vin(qldb_session, vin)
       logger.info('Getting a proof for the document.')
   
       for row in cursor:
           block_address = row.get('blockAddress')
           document_id = row.get('metadata').get('id')
   
           result = get_revision(ledger_name, document_id, block_address_to_dictionary(block_address), digest_tip_address)
           revision = result.get('Revision').get('IonText')
           document_hash = loads(revision).get('hash')
   
           proof = result.get('Proof')
           logger.info('Got back a proof: {}.'.format(proof))
   
           verified = verify_document(document_hash, digest_bytes, proof)
           if not verified:
               raise AssertionError('Document revision is not verified.')
           else:
               logger.info('Success! The document is verified.')
   
           altered_document_hash = flip_random_bit(document_hash)
           logger.info("Flipping one bit in the document's hash and assert that the document is NOT verified. "
                       "The altered document hash is: {}.".format(to_base_64(altered_document_hash)))
           verified = verify_document(altered_document_hash, digest_bytes, proof)
           if verified:
               raise AssertionError('Expected altered document hash to not be verified against digest.')
           else:
               logger.info('Success! As expected flipping a bit in the document hash causes verification to fail.')
   
           logger.info('Finished verifying the registration with VIN = {} in ledger = {}.'.format(vin, ledger_name))
   
   
   if __name__ == '__main__':
       """
       Verify the integrity of a document revision in a QLDB ledger.
       """
       registration = SampleData.VEHICLE_REGISTRATION[0]
       vin = registration['VIN']
       try:
           with create_qldb_session() as session:
               verify_registration(session, Constants.LEDGER_NAME, vin)
       except Exception:
           logger.exception('Unable to verify revision.')
   ```

------
**catatan**  
Setelah `get_revision` fungsi mengembalikan bukti untuk revisi dokumen yang ditentukan, program ini menggunakan API sisi klien untuk memverifikasi revisi tersebut.

1. Untuk menjalankan program, masukkan perintah berikut.

   ```
   python get_revision.py
   ```

Jika Anda tidak perlu lagi menggunakan `vehicle-registration` buku besar, lanjutkan ke[Langkah 8 (opsional): Bersihkan sumber daya](getting-started.python.step-8.md).

# Langkah 8 (opsional): Bersihkan sumber daya
<a name="getting-started.python.step-8"></a>

**penting**  
Pemberitahuan akhir dukungan: Pelanggan yang ada akan dapat menggunakan Amazon QLDB hingga akhir dukungan pada 07/31/2025. Untuk detail selengkapnya, lihat [Memigrasi Buku Besar QLDB Amazon ke Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

Anda dapat terus menggunakan `vehicle-registration` buku besar. Namun, jika Anda tidak lagi membutuhkannya, Anda harus menghapusnya.

**Untuk menghapus buku besar**

1. Gunakan program berikut (`delete_ledger.py`) untuk menghapus `vehicle-registration` buku besar Anda dan semua isinya.

   ```
   # 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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   from time import sleep
   
   from boto3 import client
   
   from pyqldbsamples.constants import Constants
   from pyqldbsamples.describe_ledger import describe_ledger
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   qldb_client = client('qldb')
   
   LEDGER_DELETION_POLL_PERIOD_SEC = 20
   
   
   def delete_ledger(ledger_name):
       """
       Send a request to QLDB to delete the specified ledger.
   
       :type ledger_name: str
       :param ledger_name: Name for the ledger to be deleted.
   
       :rtype: dict
       :return: Result from the request.
       """
       logger.info('Attempting to delete the ledger with name: {}...'.format(ledger_name))
       result = qldb_client.delete_ledger(Name=ledger_name)
       logger.info('Success.')
       return result
   
   
   def wait_for_deleted(ledger_name):
       """
       Wait for the ledger to be deleted.
   
       :type ledger_name: str
       :param ledger_name: The ledger to check on.
       """
       logger.info('Waiting for the ledger to be deleted...')
       while True:
           try:
               describe_ledger(ledger_name)
               logger.info('The ledger is still being deleted. Please wait...')
               sleep(LEDGER_DELETION_POLL_PERIOD_SEC)
           except qldb_client.exceptions.ResourceNotFoundException:
               logger.info('Success. The ledger is deleted.')
               break
   
   
   def set_deletion_protection(ledger_name, deletion_protection):
       """
       Update an existing ledger's deletion protection.
   
       :type ledger_name: str
       :param ledger_name: Name of the ledger to update.
   
       :type deletion_protection: bool
       :param deletion_protection: Enable or disable the deletion protection.
   
       :rtype: dict
       :return: Result from the request.
       """
       logger.info("Let's set deletion protection to {} for the ledger with name {}.".format(deletion_protection,
                                                                                             ledger_name))
       result = qldb_client.update_ledger(Name=ledger_name, DeletionProtection=deletion_protection)
       logger.info('Success. Ledger updated: {}'.format(result))
   
   
   def main(ledger_name=Constants.LEDGER_NAME):
       """
       Delete a ledger.
       """
       try:
           set_deletion_protection(ledger_name, False)
           delete_ledger(ledger_name)
           wait_for_deleted(ledger_name)
       except Exception as e:
           logger.exception('Unable to delete the ledger.')
           raise e
   
   
   if __name__ == '__main__':
       main()
   ```
**catatan**  
Jika proteksi penghapusan diaktifkan untuk buku besar Anda, Anda harus menonaktifkannya terlebih dahulu sebelum dapat menghapus buku besar menggunakan QLDB API.

   `delete_ledger.py`File ini juga memiliki ketergantungan pada program berikut (`describe_ledger.py`).

   ```
   # 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.
   #
   # This code expects that you have AWS credentials setup per:
   # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html
   from logging import basicConfig, getLogger, INFO
   
   from boto3 import client
   
   from pyqldbsamples.constants import Constants
   
   logger = getLogger(__name__)
   basicConfig(level=INFO)
   qldb_client = client('qldb')
   
   
   def describe_ledger(ledger_name):
       """
       Describe a ledger.
   
       :type ledger_name: str
       :param ledger_name: Name of the ledger to describe.
       """
       logger.info('describe ledger with name: {}.'.format(ledger_name))
       result = qldb_client.describe_ledger(Name=ledger_name)
       result.pop('ResponseMetadata')
       logger.info('Success. Ledger description: {}.'.format(result))
       return result
   
   
   def main(ledger_name=Constants.LEDGER_NAME):
       """
       Describe a QLDB ledger.
       """
       try:
           describe_ledger(ledger_name)
       except Exception as e:
           logger.exception('Unable to describe a ledger.')
           raise e
   
   
   if __name__ == '__main__':
       main()
   ```

1. Untuk menjalankan program, masukkan perintah berikut.

   ```
   python delete_ledger.py
   ```