Relationships
Of course, not all entities are ‘leaf’ objects; one of the main strengths of a database is the ability to maintain relationships between table rows, and that strength carries over to ORMs. microrm supports most standard relationship types, and all are closely integrated into the query interface.
Many-to-one (unidirectional)
The simplest relationship type is many-to-one, somewhat equivalent to a pointer where one object references a second object. These are accessible in microrm by simply storing the database ID of an entity inside another entity; this link can then be followed with the query interface, or the ID can be used directly in a following query.1 Here’s a simple example schema where every LogEntry is authored by a Person:
#[derive(Entity)]
struct Person {
#[key]
name: String
}
#[derive(Entity)]
struct LogEntry {
contents: String,
author: microrm::ID<Person>,
}
#[derive(Schema)]
struct Schema {
person: microrm::Table<Person>,
log_entry: microrm::Table<LogEntry>,
}
Assuming one starts with the ID of a specific LogEntry, the author can be queried as:
let log_entry_author =
schema.log_entry.with_id(log_entry_id).foreign(LogEntry::Author).get(txn)?;
One downside of this approach is that, given a pointed-to entity, there is no efficient way to determine what entities reference it. If you require this to be a fully bidirectional relationship, you may wish to use a many-to-many map with uniqueness constraints instead.
Many-to-many (bidirectional)
A many-to-many relationship is an arbitrary mapping between sets of two different entities. With a many-to-many relationship, microrm supports following connections in both directions; microrm implements both one-to-one and one-to-many relationships as a special case of many-to-many relationships, as described below.
Many-to-many relationships are represented as fields in both entities, along with a tag struct to allow microrm to correctly match the two sides. The Relationship trait must be implemented for this tag struct. Here’s a simple schema example of a many-to-many map of Readers to Books, storing e.g. which books a given reader has read:
struct ReadBooks;
impl microrm::Relationship for ReadBooks {
type Domain = Reader;
type Codomain = Book;
const NAME: &'static str = "ReadBooks";
}
// alternatively, can be written as:
// microrm::define_relation_tag!(ReadBooks, Reader, Book);
#[derive(Entity)]
struct Reader {
#[key]
username: String,
books_read: microrm::Domain<ReadBooks>,
}
#[derive(Entity)]
struct Book {
#[key]
isbn: String,
title: String,
read_by: microrm::Codomain<ReadBooks>,
}
(All many-to-many entity relationships in microrm have a domain and a codomain. These are generally interchangeable, but must be consistent — the domain entity contains a Domain field, and the codomain entity contains a Codomain field2.)
Actually using a relation as part of a query is done through two interfaces. To modify the relationships between existing entities, the RelationInterface trait offers two functions: connect_to() and disconnect_from(). Both Domain and Codomain also implement the full Queryable interface, so the rest of the query API can be used as well, including the insert() and delete() methods, which will correctly update relationships as relevant.
Many-to-many (unidirectional)
As a special case of many-to-many relationships, microrm provides the Map type, which:
- Is unidirectional, thus not allowing efficient ‘what references me’ queries;
- Does not require an explicit
Relationtag struct to be defined, nor does it require a field in the codomain entity; - Requires one less index, and thus is more efficient.
Note that only one Map<T> field may be present in an entity for a given T.
Many-to-many with uniqueness constraints (bidirectional)
By default, many-to-many relationships in microrm have no uniqueness constraints: every domain entity references an arbitrary set of codomain entities. There are two uniqueness constraints that may optionally be imposed: domain uniqueness and codomain uniqueness; these restrict the relationship to unique domain or codomain entities, meaning that one or both of the following is true:
- Every domain entity can reference at most one codomain entity (domain uniqueness);
- Every codomain entity is referenced by at most one domain entity (codomain uniqueness);
This can be useful in certain scenarios. In particular, these allow construction of the following relationship types:
| Domain uniqueness | Codomain uniqueness | Relationship type |
|---|---|---|
| Yes | No | Many-to-one |
| No | Yes | One-to-many |
| Yes | Yes | One-to-at-most-one |
Note that one-to-one relationships are a pending feature.
-
Specifically, storing an
EntityIDas a field in anEntitywill make microrm define a foreign key relationship with that table. ↩ -
The terms domain and codomain are taken from mathematics. ↩