Collection

A Collection is a group of Documents and associated functionality. Collections are stored on-disk using ACID-compliant, transactional storage, ensuring your data is protected in the event of a sudden power failure or other unfortunate event.

The goal of a Collection is to encapsulate the logic for a set of data in such a way that Collections could be designed to be shared and reused in multiple Schemas or applications.

Each Collection must have a unique CollectionName. To help prevent naming collisions, an authority can be specified which provides a level of namespacing.

A Collection can contain one or more Views.

Primary Keys

All documents stored in a collection have a unique id. Primary keys in BonsaiDb are immutable -- once a document has an id, it cannot be changed. If you wish for a unique key that can be updated, use a unique view, and use a separate value as a primary key.

The type is controlled by the Collection::PrimaryKey associated type. If you're using the derive macro, the type can be specified using the primary_key parameter as in this example:

#[derive(Debug, Serialize, Deserialize, Collection, Eq, PartialEq)]
#[collection(name = "multi-key", primary_key = AssociatedProfileKey)]
struct AssociatedProfileData {
    value: String,
}

#[derive(Key, Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
struct AssociatedProfileKey {
    pub user_id: u32,
    pub data_id: u64,
}

If no primary_key is specified in the derive, u64 will be used.

Inserting and accessing the collection can be done using the newly defined primary key type:

    let key = AssociatedProfileKey {
        user_id: user.header.id,
        data_id: 64,
    };
    let inserted = AssociatedProfileData {
        value: String::from("hello"),
    }
    .insert_into(&key, &db)?;
    let retrieved = AssociatedProfileData::get(&key, &db)?.expect("document not found");
    assert_eq!(inserted, retrieved);

Natural Ids

It's not uncommon to need to store data in a database that has an "external" identifier. Some examples could be externally authenticated user profiles, social networking site posts, or for normalizing a single type's fields across multiple Collections. These types of values are often called "Natural Keys" or "Natural Identifiers".

SerializedCollection::natural_id() or DefaultSerialzation::natural_id can be implemented to return a value from the contents of a new document. When using the derive marco, the natural_id parameter can be specified with either a closure or a path to a function with the same signature.

In this example, the UserProfile type is used to represent a user that has a unique ID in an external database:

#[derive(Debug, Serialize, Deserialize, Collection, Eq, PartialEq)]
#[collection(name = "user-profiles", primary_key = u32)]
struct UserProfile {
    #[natural_id]
    pub external_id: u32,
    pub name: String,
}

When pushing a UserProfile into the collection, the id will automatically be assigned by calling natural_id():

    let user = UserProfile {
        external_id: 42,
        name: String::from("ecton"),
    }
    .push_into(&db)?;
    let retrieved_from_database = UserProfile::get(&42, &db)?.expect("document not found");
    assert_eq!(user, retrieved_from_database);

Custom Primary Keys

All primary keys must implement the Key trait . BonsaiDb provides implementations for many types, but any type that implements the trait can be used.

When using push/push_into, BonsaiDb needs to assign a unique ID to the incoming document. If natural_id() returns None, the storage backend will handle id assignment.

If the document being pushed is the first document in the collection, Key::first_value() is called and the resulting value is used as the document's id.

If the collection already has documents, the highest-ordered key is queried from the collection. Key::next_value() is then called and the resulting value is used as the document's id. Key implementors should not allow next_value() to return a value that is less than the current value. NextValueError::WouldWrap should be returned instead of wrapping.

Both first_value() and next_value() by default return NextValueError::Unimplemented. If any error occurs while trying to assign a unique id, the transaction will be aborted and rolled back.