How to use preupgrade and postupgrade to upgrade HashMap to Trie

Hey guys, I used HashMap before. Now I want to use preupgrade and postupgrade to copy the data in userMap to userTrie. I found that Inter has a toList function and Trie has fromList function. Does anyone know how to use these two functions? Or is there any other way? thx.

var userMap = HashMap.HashMap<Text, User>(1, isEq, Text.hash); 
stable var userTrie: Trie.Trie<Text, User> = Trie.empty(); 
system func preupgrade() {};
system func postupgrade() {};

I tried to add the following code in preupgrade, but it didn’t take effect. The data in userMap was not copied to userTrie.

system func preupgrade() {
  for ((x, y) in userMap.entries()) {
    userTrie:=Trie.put(userTrie,textKey(x),Text.equal,y).0;
  };
};

Any particular reason to use a Trie instead of an Array (as discussed / displayed here) as stable memory to process the upgrade?

Note this:

So the preupgrade function you’ve just installed didn’t run on that install, but it’s now in the canister and will run when you next upgrade.

(Presumably you’ve already been copying the HashMap to stable storage alongside the code you’ve shown here).

Hey @Ori thank you for your reply

My steps are as follows
step 1. I used dfx deploy to deploy the code, including preupgrade() code.
step 2. I add some data to userMap.
step 3. Then I used dfx deploy again to deploy the code, but the data in userMap was not copied to userTrie.

However, if I delete the code of preupgrade() in step 2, and then execute step 3, then the data in userMap can be copied to userTrie.

I think preupgrade() is executed every time I update the canister. In step 1, I have deployed it, and in step 3, it should be executed when I deploy it again.

Did I do something wrong?

Hi @Ori , here is my code, thank you so much for helping me many times. :sweat_smile:

import HashMap "mo:base/HashMap";
import Text "mo:base/Text";
import Nat "mo:base/Nat";
import Trie "mo:base/Trie";

actor{
    //step1: dfx deploy

    //step2: Add test data to userMap
    //dfx canister call  <canister> addTestData '(10)'

    //step3: dfx deploy

    //step4: Check the number of data in userTrie to determine whether the update is successful
    //dfx canister call <canister> getUserCount
    
 

    func isEq(x: Text, y: Text): Bool { x == y };
    func textKey(x : Text) : Trie.Key<Text> {
        return { key = x; hash = Text.hash(x) };
    };
    var userMap = HashMap.HashMap<Text, Text>(1, isEq, Text.hash); 
    stable var userTrie:Trie.Trie<Text, Text> = Trie.empty(); 

    public func addTestData(makeCount:Nat){
        var countNum=0;
        while(countNum < makeCount){
            var  account:Text= Nat.toText(countNum);
            userMap.put(Nat.toText(countNum),account);
            countNum:=countNum+1;
        };
    };

    public func getUserCount():async Int{
        var userCount=Trie.size(userTrie);
        userCount
    };

    system func preupgrade() {
        for ((x, y) in userMap.entries()) {
            userTrie:=Trie.put(userTrie,textKey(x),Text.equal,y).0;
        };
    };

    system func postupgrade() {

    };

}

Thanks to everyone, the problem is solved, here is the solution, I hope to help others in the future.

step1:
Add an array:
stable var userEntries: [(Text, Text)] = [];

In preupgrade(), copy the data of userMap to userEntries

system func preupgrade() {
   userEntries := Iter.toArray(userMap.entries());
};

step2:
dfx deploy

step3:
Increase stable var userTrie:Trie.Trie<Text, Text> = Trie.empty();
In postupgrade, copy the data of userEntries to userTrie

    system func postupgrade() {
        for ((x, y) in userEntries.vals()) {
              userTrie:=Trie.put(userTrie,textKey(x),Text.equal,y).0;
        };
    };

step4:
dfx deploy

2 Likes