Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

docs: add docs on how to upgrade the schema of stable structures #146

Merged
merged 3 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ For more information about the philosophy behind the library, see [Roman's tutor
- [Cell]: A serializable value
- [MinHeap]: A priority queue.

## Tutorials

[Schema Upgrades](./docs/schema-upgrades.md)

## How it Works

Stable structures are able to work directly in stable memory because each data structure manages
Expand Down
66 changes: 66 additions & 0 deletions docs/schema-upgrades.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Schema Upgrades

Stable structures store data directly in stable memory and do not require upgrade hooks.
Since these structures are designed to persist throughout the lifetime of the canister, it's nearly inevitable that developers would want to make modifications to the data's schema as the canister evolves.

Let's say you are storing assets in your canister. The declaration of it can look something like this:

```rust
#[derive(Serialize, Deserialize, CandidType)]
struct Asset {
// The contents of the asset.
contents: Vec<u8>,
}

impl Storable for Asset {
fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
let mut bytes = vec![];
ciborium::ser::into_writer(&self, &mut bytes).unwrap();
Cow::Owned(bytes)
}

fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
ciborium::de::from_reader(&*bytes).expect("deserialization must succeed.")
}

const BOUND: Bound = Bound::Unbounded;
}
```

> **Note:** Stables structures do not enforce a specific data format.
It's up to the developer to use the data format that fits their use-case.
In this example, CBOR is used for encoding `Asset`.


## Adding an attribute

Adding a new field can be as simple as adding the field, like this:

```rust
#[derive(Serialize, Deserialize)]
struct Asset {
// The contents of the asset.
contents: Vec<u8>,

// The timestamp the asset was created at.
#[serde(default)]
created_at: u64,
}
```

If the new attribute being added doesn't have a sensible default value, consider wrapping it in an `Option`:

```rust
#[derive(Serialize, Deserialize, CandidType)]
struct Asset {
// The contents of the asset.
contents: Vec<u8>,

// The timestamp the asset was created at.
#[serde(default)]
created_at: u64,

// The username of the uploader.
uploaded_by: Option<String>,
}
```