Skip to content

Instantly share code, notes, and snippets.

@anthonymq
Last active July 29, 2021 14:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anthonymq/702a43d1258a6810a7f4fcfcd569d55b to your computer and use it in GitHub Desktop.
Save anthonymq/702a43d1258a6810a7f4fcfcd569d55b to your computer and use it in GitHub Desktop.
CanisterUpgrade
import TrieMap "mo:base/TrieMap";
import Iter "mo:base/Iter";
import Nat32 "mo:base/Nat32";
import Debug "mo:base/Debug";
shared ({caller = initPrincipal}) actor class Example () {
public type State = {
db: TrieMap.TrieMap<Nat32, Card>;
};
func empty () : State {
{
db = TrieMap.TrieMap<Nat32, Card>(Nat32.equal, nat32ToHash);
}
};
public type Card = {
title:Text;
};
func nat32ToHash (n : Nat32) : Nat32 {
return (n);
};
stable var dbUpgrade : [(Nat32, Card)] = [];
var state : State= empty();
public shared func insertData() : async () {
state.db.put(0,{title="test1"});
state.db.put(1,{title="test2"});
state.db.put(3,{title="test3"});
};
system func preupgrade() {
Debug.print("Begin preupgrade");
dbUpgrade := Iter.toArray(state.db.entries());
for((k,v) in Iter.fromArray(dbUpgrade)) {
Debug.print("cardId : " # Nat32.toText(k));
};
Debug.print("End preupgrade");
};
system func postupgrade() {
Debug.print("Begin postupgrade");
for((k,v) in Iter.fromArray(dbUpgrade)) {
state.db.put(k,v);
Debug.print("cardId : " # Nat32.toText(k));
};
Debug.print("End postupgrade");
};
}
[Canister qoctq-giaaa-aaaaa-aaaea-cai] Begin preupgrade
[Canister qoctq-giaaa-aaaaa-aaaea-cai] cardId : 0
[Canister qoctq-giaaa-aaaaa-aaaea-cai] cardId : 1
[Canister qoctq-giaaa-aaaaa-aaaea-cai] cardId : 3
[Canister qoctq-giaaa-aaaaa-aaaea-cai] End preupgrade
[Canister qoctq-giaaa-aaaaa-aaaea-cai] Begin postupgrade
[Canister qoctq-giaaa-aaaaa-aaaea-cai] cardId : 0
[Canister qoctq-giaaa-aaaaa-aaaea-cai] cardId : 1
[Canister qoctq-giaaa-aaaaa-aaaea-cai] cardId : 3
[Canister qoctq-giaaa-aaaaa-aaaea-cai] End postupgrade
import TrieMap "mo:base/TrieMap";
import Iter "mo:base/Iter";
import Nat32 "mo:base/Nat32";
import Debug "mo:base/Debug";
shared ({caller = initPrincipal}) actor class Example () {
public type State = {
db: TrieMap.TrieMap<Nat32, Card>;
};
func empty () : State {
{
db = TrieMap.TrieMap<Nat32, Card>(Nat32.equal, nat32ToHash);
}
};
public type Card = {
title:Text;
description: Text;
};
func nat32ToHash (n : Nat32) : Nat32 {
return (n);
};
stable var dbUpgrade : [(Nat32, Card)] = [];
var state : State= empty();
public shared func insertData() : async () {
state.db.put(0,{title="test1"; description="desc"});
state.db.put(1,{title="test2"; description="desc"});
state.db.put(3,{title="test3"; description="desc"});
};
system func preupgrade() {
Debug.print("Begin preupgrade");
dbUpgrade := Iter.toArray(state.db.entries());
for((k,v) in Iter.fromArray(dbUpgrade)) {
Debug.print("cardId : " # Nat32.toText(k));
};
Debug.print("End preupgrade");
};
system func postupgrade() {
Debug.print("Begin postupgrade");
for((k,v) in Iter.fromArray(dbUpgrade)) {
state.db.put(k,v);
Debug.print("cardId : " # Nat32.toText(k));
};
Debug.print("End postupgrade");
};
}
[Canister qoctq-giaaa-aaaaa-aaaea-cai] Begin preupgrade
[Canister qoctq-giaaa-aaaaa-aaaea-cai] cardId : 0
[Canister qoctq-giaaa-aaaaa-aaaea-cai] cardId : 1
[Canister qoctq-giaaa-aaaaa-aaaea-cai] cardId : 3
[Canister qoctq-giaaa-aaaaa-aaaea-cai] End preupgrade
[Canister qoctq-giaaa-aaaaa-aaaea-cai] Begin postupgrade
[Canister qoctq-giaaa-aaaaa-aaaea-cai] End postupgrade
@crusso
Copy link

crusso commented Jul 29, 2021

Here's another version of step2.mo that works by instead introducing a new stable var, with the new type, initialized from the old.

step2.mo

import TrieMap "mo:base/TrieMap";
import Iter "mo:base/Iter";
import Nat32 "mo:base/Nat32";
import Debug "mo:base/Debug";
import Array "mo:base/Array";

shared ({caller = initPrincipal}) actor class Example () {

    public type State = {
        db: TrieMap.TrieMap<Nat32, Card>;
    };

    func empty () : State {
        {
            db = TrieMap.TrieMap<Nat32, Card>(Nat32.equal, nat32ToHash);
        }
    };

    public type Card = {
        title:Text;
        description: Text;
    };

    func nat32ToHash (n : Nat32) : Nat32 {
      return (n);
    };

    // rename the old card type for clarity
    module Old {
       public type Card = {
          title:Text;
       };
    };

    stable var dbUpgrade : [(Nat32, Old.Card)] = [];

    // add a new stable var with the incompatible representation, initialized
    // from the preserved, original stable var
    stable var dbUpgradeNew : [(Nat32, Card)] =
       Array.map<(Nat32, Old.Card),(Nat32, Card)>(
         dbUpgrade,
         func (n, c) { (n,{ title = c.title; description="desc" }) });

    dbUpgrade := []; // discard the original stable var

    var state : State = empty();

    public shared func insertData() : async () {
        state.db.put(0,{title="test1"; description="desc"});
        state.db.put(1,{title="test2"; description="desc"});
        state.db.put(3,{title="test3"; description="desc"});
    };

    system func preupgrade() {
        Debug.print("Begin preupgrade");
        dbUpgradeNew := Iter.toArray(state.db.entries());
        for((k,v) in Iter.fromArray(dbUpgradeNew)) {
            Debug.print("cardId : " # Nat32.toText(k));
        };
        Debug.print("End preupgrade");
    };

    system func postupgrade() {
        Debug.print("Begin postupgrade");
        for((k,v) in Iter.fromArray(dbUpgradeNew)) {
            state.db.put(k,v);
            Debug.print("cardId : " # Nat32.toText(k));
        };
        Debug.print("End postupgrade");
    };

}

Once you upgrade from step1 to step2, you can partially clean up step2.mo by promoting the type of the original stable var to Any (so it's effectively useless).

step3.mo

import TrieMap "mo:base/TrieMap";
import Iter "mo:base/Iter";
import Nat32 "mo:base/Nat32";
import Debug "mo:base/Debug";

shared ({caller = initPrincipal}) actor class Example () {

    public type State = {
        db: TrieMap.TrieMap<Nat32, Card>;
    };

    func empty () : State {
        {
            db = TrieMap.TrieMap<Nat32, Card>(Nat32.equal, nat32ToHash);
        }
    };

    public type Card = {
        title:Text;
        description: Text;
    };

    func nat32ToHash (n : Nat32) : Nat32 {
      return (n);
    };

    stable var dbUpgrade : Any = [];

    stable var dbUpgradeNew : [(Nat32, Card)] = [];

    var state : State = empty();

    public shared func insertData() : async () {
        state.db.put(0,{title="test1"; description="desc"});
        state.db.put(1,{title="test2"; description="desc"});
        state.db.put(3,{title="test3"; description="desc"});
    };

    system func preupgrade() {
        Debug.print("Begin preupgrade");
        dbUpgradeNew := Iter.toArray(state.db.entries());
        for((k,v) in Iter.fromArray(dbUpgradeNew)) {
            Debug.print("cardId : " # Nat32.toText(k));
        };
        Debug.print("End preupgrade");
    };

    system func postupgrade() {
        Debug.print("Begin postupgrade");
        for((k,v) in Iter.fromArray(dbUpgradeNew)) {
            state.db.put(k,v);
            Debug.print("cardId : " # Nat32.toText(k));
        };
        Debug.print("End postupgrade");
    };

}

I find these steps quite ugly, so prefer the approach in the Life example of using a variant to control the version of the stable var. That way you just have introduce a new variant when the representation changes, without having to declare new stable variables (eg. dbUpgradeNew). But it does require you to anticipate that you might want to change the representation when you set out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment