Document Object Model

This library abstracts much of the QLDB implementation away from its user. All the user has to do is create a Document object, add fields to it and then call save(). Under the hood, the library will translate the Document fields into PartiQL queries and use the pyqldb Driver to post the queries to the QLDB instance on AWS.

.. note:: All documents are indexed through the key field id.

Saving

If you have the LEDGER environment variable set, all that is required is to create a Document object and pass it the table name from the QLDB ledger. If the following lines are feed into an interactive Python shell or copied into a script,

from qldb-orm.qldb import Document

my_document = Document('table_name')
my_document.property_one = 'property 1'
my_document.property_two = 'property 2'
my_document.save()

Then a document will be inserted into the QLDB ledger table. If you do not have the LEDGER environment variable set, you must pass in the ledger name along with the table name through named arguments,

from qldb-orm.qldb import Document

my_document = Document(table='table_name', ledger='ledger_name')
my_document.property_one = 'property 1'
my_document.property_two = 'property 2'
my_document.save()

.. note:: The Document class will auto-generate a UUID for each document inserted into the ledger table.

Congratulations! You have saved a document to QLDB!

Loading

To load a document that exists in the ledger table already, pass in the id of the Document when creating a new instance,

from qldb-orm.qldb import Document

my_document(table='table_name', id='12345')
print(my_document.property_one)

Updating

Updating and saving are different operations, in terms of the PartiQL queries that implement these operations, but from the Document’s perspective, they are the same operation; the same method is called in either case. The following script will save a value of test 1 to field and then overwrite it with a value of test 2,

from qldb-orm.qldb import Document

my_document = Document('table_name')
my_document.field = 'test 1'
my_document.save()
my_document.field = 'test 2'
my_document.save()
print(my_document.id)
print(my_document.field)

Behind the scenes, whenever the save() method is called, a query is run to check for the existence of the given Document. If the Document doesn’t exist, the library will create a new one. If the Document does exist, the library will overwrite the existing Document.

Fields

The document fields can be returned as a dict through the fields() method. The following script will loop through the fields on an existing document with id=test and print their corresponding values,

from qldb-orm.qldb import Document

my_document = Document(table='table_name', id='test')

for key, value in my_document.fields().items():
  print(key, '=', value)

Native Object Attribute Nesting

A document returned in nested format,

{
  "prop_1": {
    "prop_2": {
      "prop_3": {
        "prop_4" : 5
      }
    }
  }
}

is deserialized into nestable attributes on the Document object, i.e.

assert document.prop_1.prop_2.prop_3.prop_4 == 5

Document Strands

QLDB is an immutable ledger, meaning it stores the entire history of transactions. This feature can be used to construct snapshots of a document across time. If you want to initialized a document with its full history, use the stranded argument when constructing a new object or loading an existing document into an object. Each strand will be accessible as a nested Document on the current Document

from qldb-orm.qldb import Document

doc = Document('test_table', id='12345', stranded=True)

# strands are accessible through ordered array `doc.strands`
for i, strand in enumerate(doc.strands):
    print('Strand #', i)
    # prints `true`
    print(isinstance(strand, Document))
    print(strand.fields())

Query Object Model

Queries are represented as an object, Query. Each Query must be initialized with a table that it will run PartialQL queries against. All queries return a list of Document objects.

All

The following script queries the ledger table for all documents and prints a JSON representation of each document to screen,

from qldb-orm.qldb import Query

all_documents = Query('table_name').get_all()
for document in all_documents:
  print(document.fields())

History

One of the unique features of QLDB is its immutability; as a result of its implementation, QLDB keeps a log of all transactions that have occured on a table. This features is accessible in PartiQL through the history() function in the query SELECT * FROM history(table). The Query class provides a wrapper around this query that parses it into a collection of Documents, i.e. the transaction history is traversible in the same way a document model is,

from qldb-orm.qldb import Query

transaction = Query('table_name').history()

for result in results:
  print(result.data)
  print(result.metadata)

Find By

The find_by() method accepts **kwarg aruments for any of the fields you want to query by; Note, the query is filtering on equality, i.e. it searches for all documents where the fields exactly equal their specified values.

from qldb-orm.qldb import Query

search_documents = Query('table_name').find_by(company='Makpar')
search_documents_kwargs = Query('table_name').find_by(**{ 'company' : 'Makpar', 'department': 'Innovation' })

Find In

This method will execute a SELECT* FROM table WHERE a in (?, ? ... ? ) PartiQL query. The find_in methods accepts **kwarg arguments for any of the fields you want to query by, where the values of each keyword are an array of values to search by. For example,

from qldb-orm.qldb import Query

search_documents = Query('table_name').find_in(company=['Makpar','Company'], number=[1,2,3])

will execute the following query,

SELECT * FROM table_name WHERE company IN ('Makpar', 'Company') AND number IN (1, 2, 3)