New ACM paper, free-tier cloud, and open-source license

Lesson 11.2: Programmatic retrieval

Retrieving objects programmatically

In addition to retrieving stateful objects via Get queries, we can also retrieve them programmatically via API calls. Type objects can be retrieved through the concepts property of a transaction object, as in the following example.

with TypeDB.core_driver(ADDRESS) as driver:
    with driver.session(DATABASE, SessionType.DATA) as session:
        with session.transaction(TransactionType.READ) as transaction:
            book_type: EntityType = transaction.concepts.get_entity_type("book").resolve()
            contribution_type: RelationType = transaction.concepts.get_relation_type("contribution").resolve()
            page_count_type: AttributeType = transaction.concepts.get_attribute_type("page-count").resolve()

The methods get_entity_type, get_relation_type, and get_attribute_type are used to retrieve entity, relation, and attribute types respectively, and take the type label as an argument. These methods return promises, and so must be resolved using their resolve methods in order to obtain the responses.

We can also retrieve the roles in a given relation type by calling its get_relates method, as follows. This method requires a transaction as an argument.

with TypeDB.core_driver(ADDRESS) as driver:
    with driver.session(DATABASE, SessionType.DATA) as session:
        with session.transaction(TransactionType.READ) as transaction:
            contribution_type: RelationType
            contribution_roles: Iterator[RoleType] = contribution_type.get_relates(transaction)

Most API methods that retrieve information from the server require a transaction as an argument and return either iterators or promises. An iterator can be resolved by iterating over it or casting it to a sequence (such as a list), while a promise can be resolved by calling its resolve method. The return types of all Python driver methods are documented in the API reference. TypeDB drivers in other languages have equivalent functionality.

Once an entity, relation, or attribute type has been retrieved as a stateful object, we can retrieve all instances of the type as stateful objects using the type’s get_instances method.

with TypeDB.core_driver(ADDRESS) as driver:
    with driver.session(DATABASE, SessionType.DATA) as session:
        with session.transaction(TransactionType.READ) as transaction:
            book_type: EntityType
            contribution_type: RelationType
            page_count_type: AttributeType

            books: Iterator[Entity] = book_type.get_instances(transaction)
            contributions: Iterator[Relation] = contribution_type.get_instances(transaction)
            page_counts: Iterator[Attribute] = page_count_type.get_instances(transaction)

Sometimes, retrieving data instances this way can be more convenient than issuing a query, but most non-trivial constraints on the data instances to be returned can only be expressed by using a query. More importantly, we can also operate on the type object to insert new instances, as we will see in Lesson 11.3, and to perform programmatic schema migrations.

Exercise

Write a function that yields all users in the database as stateful objects. Write the function once by executing a TypeQL query, and again without using any TypeQL. The function should have the following signature.

get_users(transaction: TypeDBTransaction) -> Iterator[Entity]

You may find it useful to refer to the bookstore’s schema.

Schema
define

book sub entity,
    abstract,
    owns isbn-13 @key,
    owns isbn-10 @unique,
    owns title,
    owns page-count,
    owns genre,
    owns price,
    plays contribution:work,
    plays publishing:published,
    plays promotion-inclusion:item,
    plays order-line:item,
    plays rating:rated,
    plays recommendation:recommended;

hardback sub book,
    owns stock;

paperback sub book,
    owns stock;

ebook sub book;

contributor sub entity,
    owns name,
    plays contribution:contributor,
    plays authoring:author,
    plays editing:editor,
    plays illustrating:illustrator;

company sub entity,
    abstract,
    owns name;

publisher sub company,
    plays publishing:publisher;

courier sub company,
    plays delivery:deliverer;

publication sub entity,
    owns year,
    plays publishing:publication,
    plays locating:located;

user sub entity,
    owns id @key,
    owns name,
    owns birth-date,
    plays action-execution:executor,
    plays locating:located,
    plays recommendation:recipient;

order sub entity,
    owns id @key,
    owns status,
    plays order-line:order,
    plays action-execution:action,
    plays delivery:delivered;

promotion sub entity,
    owns code @key,
    owns name,
    owns start-timestamp,
    owns end-timestamp,
    plays promotion-inclusion:promotion;

review sub entity,
    owns id @key,
    owns score,
    owns verified,
    plays rating:review,
    plays action-execution:action;

login sub entity,
    owns success,
    plays action-execution:action;

address sub entity,
    owns street,
    plays delivery:destination,
    plays locating:located;

place sub entity,
    abstract,
    owns name,
    plays locating:located,
    plays locating:location;

city sub place;

state sub place;

country sub place;

contribution sub relation,
    relates contributor,
    relates work;

authoring sub contribution,
    relates author as contributor;

editing sub contribution,
    relates editor as contributor;

illustrating sub contribution,
    relates illustrator as contributor;

publishing sub relation,
    relates publisher,
    relates published,
    relates publication;

promotion-inclusion sub relation,
    relates promotion,
    relates item,
    owns discount;

order-line sub relation,
    relates order,
    relates item,
    owns quantity,
    owns price;

rating sub relation,
    relates review,
    relates rated;

action-execution sub relation,
    relates action,
    relates executor,
    owns timestamp;

delivery sub relation,
    relates deliverer,
    relates delivered,
    relates destination;

locating sub relation,
    relates located,
    relates location;

recommendation sub relation,
    relates recommended,
    relates recipient;

isbn sub attribute, abstract, value string;
isbn-13 sub isbn;
isbn-10 sub isbn;
title sub attribute, value string;
page-count sub attribute, value long;
genre sub attribute, value string;
stock sub attribute, value long;
price sub attribute, value double;
discount sub attribute, value double;
id sub attribute, value string;
code sub attribute, value string;
name sub attribute, value string;
birth-date sub attribute, value datetime;
street sub attribute, value string;
year sub attribute, value long;
quantity sub attribute, value long;
score sub attribute, value long;
verified sub attribute, value boolean;
timestamp sub attribute, value datetime;
start-timestamp sub attribute, value datetime;
end-timestamp sub attribute, value datetime;
status sub attribute, value string, regex "^(paid|dispatched|delivered|returned|canceled)$";
success sub attribute, value boolean;

rule review-verified-by-purchase:
    when {
        ($review, $product) isa rating;
        ($order, $product) isa order-line;
        ($user, $review) isa action-execution, has timestamp $review-time;
        ($user, $order) isa action-execution, has timestamp $order-time;
        $review-time > $order-time;
    } then {
        $review has verified true;
    };

rule review-unverified:
    when {
        $review isa review;
        not { $review has verified true; };
    } then {
        $review has verified false;
    };

rule book-recommendation-by-genre:
    when {
        $user isa user;
        $liked-book isa book;
        {
            ($user, $order) isa action-execution;
            ($order, $liked-book) isa order-line;
        } or {
            ($user, $review) isa action-execution;
            ($review, $liked-book) isa rating;
            $review has score >= 7;
        };
        $new-book isa book;
        not { {
            ($user, $order) isa action-execution;
            ($order, $new-book) isa order-line;
        } or {
            ($user, $review) isa action-execution;
            ($review, $new-book) isa rating;
        }; };
        $liked-book has genre $shared-genre;
        $new-book has genre $shared-genre;
        not { {
            $shared-genre == "fiction";
        } or {
            $shared-genre == "nonfiction";
        }; };
    } then {
        (recommended: $new-book, recipient: $user) isa recommendation;
    };

rule book-recommendation-by-author:
    when {
        $user isa user;
        $liked-book isa book;
        {
            ($user, $order) isa action-execution;
            ($order, $liked-book) isa order-line;
        } or {
            ($user, $review) isa action-execution;
            ($review, $liked-book) isa rating;
            $review has score >= 7;
        };
        $new-book isa book;
        not { {
            ($user, $order) isa action-execution;
            ($order, $new-book) isa order-line;
        } or {
            ($user, $review) isa action-execution;
            ($review, $new-book) isa rating;
        }; };
        ($liked-book, $shared-author) isa authoring;
        ($new-book, $shared-author) isa authoring;
    } then {
        (recommended: $new-book, recipient: $user) isa recommendation;
    };

rule order-line-total-retail-price:
    when {
        ($order) isa action-execution, has timestamp $order-time;
        $line ($order, $item) isa order-line;
        not {
            ($promotion, $item) isa promotion-inclusion;
            $promotion has start-timestamp <= $order-time,
                has end-timestamp >= $order-time;
        };
        $item has price $retail-price;
        $line has quantity $quantity;
        ?line-total = $quantity * $retail-price;
    } then {
        $line has price ?line-total;
    };

rule order-line-total-discounted-price:
    when {
        ($order) isa action-execution, has timestamp $order-time;
        $line ($order, $item) isa order-line;
        ($best-promotion, $item) isa promotion-inclusion, has discount $best-discount;
        $best-promotion has start-timestamp <= $order-time,
            has end-timestamp >= $order-time;
        not {
            ($other-promotion, $item) isa promotion-inclusion, has discount > $best-discount;
            $other-promotion has start-timestamp <= $order-time,
                has end-timestamp >= $order-time;
        };
        $item has price $retail-price;
        ?discounted-price = round(100 * $retail-price * (1 - $best-discount)) / 100;
        $line has quantity $quantity;
        ?line-total = $quantity * ?discounted-price;
    } then {
        $line has price ?line-total;
    };

rule transitive-location:
    when {
        (location: $parent-place, located: $child-place) isa locating;
        (location: $child-place, located: $x) isa locating;
    } then {
        (location: $parent-place, located: $x) isa locating;
    };
Sample solution

With TypeQL:

def get_users(transaction: TypeDBTransaction) -> Iterator[Entity]:
    results = transaction.query.get("""
        match
        $user isa user;
        get;
    """)

    for result in results:
        user = result.get("user").as_entity()

        yield user

Without TypeQL:

def get_users(transaction: TypeDBTransaction) -> Iterator[Entity]:
    user_type = transaction.concepts.get_entity_type("user").resolve()
    users = user_type.get_instances(transaction)
    return users

Retrieving associated objects

Once we have retrieved a stateful object, we can use its methods to retrieve associated objects. For example, to retrieve the attributes that an entity or relation object owns, we can use its get_has method, and to retrieve the relations that it plays as role in, we can use its get_relations method. Both of these methods return the results as stateful objects. In the following code snippet, we retrieve the attributes of a book and the relations it is in.

with TypeDB.core_driver(ADDRESS) as driver:
    with driver.session(DATABASE, SessionType.DATA) as session:
        with session.transaction(TransactionType.READ) as transaction:
            book: Entity
            attributes: Iterator[Attribute] = book.get_has(transaction)
            relations: Iterator[Relation] = book.get_relations(transaction)

In addition to these two methods, there are a number of others for retrieving associated objects. The most useful methods are summarised in the following table.

Method Return type Usage

Entity.get_has, Relation.get_has

Iterator[Attribute]

Retrieves the attributes of an entity or relation. Attribute types may be optionally specified.

Entity.get_relations, Relation.get_relations

Iterator[Relation]

Retrieves the relations in which an entity or relation plays a role. Roles may be optionally specified.

Attribute.get_owners

Iterator[Thing]

Retrieves the owners of an attribute. Owner types may be optionally specified.

Relation.get_players_by_role_type

Iterator[Thing]

Retrieves the roleplayers in a relation. Roles may be optionally specified.

Because the owners of an attribute or the roleplayers in a relation can be both entities and relations, the objects returned by get_owners and get_players_by_role_type are of the generic Thing class. They can be cast to the appropriate type using the following helper function or similar.

def cast_thing(thing: Thing) -> Entity | Relation:
    if thing.is_entity():
        return thing.as_entity()
    if thing.is_relation():
        return thing.as_relation()

The Thing class is deprecated and will be removed in TypeDB 3.0.

The methods in the above table can also take types or roles as optional arguments on which to filter the data instances returned, as in the following example. This time, we only retrieve the contributions a book plays the work role in, rather than all relations.

with TypeDB.core_driver(ADDRESS) as driver:
    with driver.session(DATABASE, SessionType.DATA) as session:
        with session.transaction(TransactionType.READ) as transaction:
            book: Entity
            work_role: RoleType
            contributions = book.get_relations(transaction, work_role)
Exercise

Write a function that yields the entities that are related to a given entity by a relation of any type. The function should have the following signature.

get_related_entities(transaction: TypeDBTransaction, entity: Entity) -> Iterator[Entity]
Hint

For each roleplayer yielded, you will need to ensure that it is an entity, and that it is not the original entity.

Sample solution
def get_related_entities(transaction: TypeDBTransaction, entity: Entity) -> Iterator[Entity]:
    for relation in entity.get_relations(transaction):
        for player in relation.get_players_by_role_type(transaction):
            if not player.is_entity():
                continue
            elif player == entity:
                continue
            else:
                yield player.as_entity()

Provide Feedback