Entry links vs Vec<Address>

I have a nested document that I want to store as Entries based on all contained objects. Important is that the objects are hashed based on the content of their nested objects.

I got the impression that Links are used for mutable or ad-hoc associations between Entries. So naively I wrote nested structs with address references:

#[derive(Serialize, Deserialize, Debug, DefaultJson, Clone)]
pub struct Beat {
    notes: Vec<Address>
}

#[derive(Serialize, Deserialize, Debug, DefaultJson, Clone)]
pub struct Note {
    pitch: u8,
}

Now I want to get the Beat Entry and return it as a JSON tree, including the Note. The client should not have to do the querying for the rest of the substructure. I started out retrieving the Entry:

#[zome_fn("hc_public")]
    fn get_beat(address: Address) -> ZomeApiResult<Option<Entry>> {
        let beat = hdk::get_entry(&address);
        // the beat does not contain the nested Note, only its Vec<Address>
        // use get_links_and_load? does that work for address references?
        ...
    }

But now I’m in a predicament. I could recursively go over the addresses in the resulting struct and retrieve the sub-entries, but then there is no structure available to build up to render the actual JSON tree unless I duplicate the Beat and Note structs to support struct nesting:

#[derive(Serialize, Deserialize, Debug, DefaultJson, Clone)]
pub struct Beat2 {
    notes: Vec<Note>
}

I could use links and leave out the Vec<Address>, but do link contents get hashed along with the Entry? Or do hdk functions exist to retrieve nested Entry structures?

Hi @ldwm, maybe this is useful here: https://hackmd.io/RZn-QeU8TKuhBU-58IvRzg

1 Like

Thank you! This addresses the question of identity. Vec seems indeed relevant for this context.

So do I understand it right that document rebuilding is left up to the developer or are there recursive entry-get mechanics available?

What do you mean by document rebuilding, regarding entities or links? What is a document in this context?

There are a few structs in Rust that refer to each other using “implicit links”, together creating a document.

Given that the data structure looks like this,

#[derive(Serialize, Deserialize, Debug, DefaultJson, Clone)]
pub struct Beat {
    notes: Vec<Address>
}

#[derive(Serialize, Deserialize, Debug, DefaultJson, Clone)]
pub struct Note {
    pitch: u8,
}

How would one return from the zome call something like this:

{result: {
    notes: [
        {pitch: 60}
    ]
}}

Ideally, it would be something like…

get_nested_and_merge(beat_address) //Beat(notes: Vec(Note(pitch: 60)))

Thank you for your help!

Yeah no, there is no built-in function for that, since holochain can’t know that your notes field refers to holochain entries.

BTW, are you storing only one byte in the Note entry for some special reason?

No, this is a contrived example for simplicity. The actual data structure is not so simple and goes much deeper.

1 Like

This section looks like it refers to a graphql style concept of data fetching, I haven’t learned the inner workings of it yet, but I think the hc-happ-create code generator sets you up for doing graphql style stuff. I am not sure if it would accommodate this kind of functionality you’re looking for.

Thanks! They actually do it in the frontend here using multiple calls, but I would to minimize that to a single call and do the structure merging on the backend (in the zome).

I wrote a proc macro that successfully creates tree-versions of user Entry structures, so it can stitch the Entries back together and return them as nested results from Zome HTTP API calls.

[nix-shell:/vagrant]$ curl -X POST -H "Content-Type: application/json" -d '{"id": "0", "jsonrpc": "2.0", "method": "call", "params": {"instance_id": "test-instance", "zome": "store", "function": "get_score", "args": { "address": "QmckLgTLTpyM4G7AsiLwkaC2ki9un864NNjHgptDcpVCPu"} }}' http://127.0.0.1:8888

# {"jsonrpc":"2.0","result":"{\"Ok\":{\"address\":\"QmThDfE1VeCnXrHsctp53xe6nrbL6Gzm1GcGbDYMWbQ2bC\",\"parts\":[{\"address\":\"QmSv2wvkg16w1jBtdDRNR6YB3DeenEBY4b7im7yAQXLSU8\",\"beats\":[{\"address\":\"QmeCjKfyZsaKms9fcXqFnCPmZAYHvXXgzuZPRocAY54mEw\",\"duration\":{\"address\":\"Qmbb1ym1YKpzp1AEHzg7kRec9xNDpNtZSAJuRJe7BrumbE\",\"dots\":0,\"typing\":\"Whole\"},\"notes\":[{\"address\":\"QmPxBzQfSe5zqz8oZC9V3MsN1xdvnzVEM9gFw6PqUzvW8R\",\"pitch\":{\"address\":\"Qmcu2bg3MKYFjbNHiVwjbgdx7Y3bSbhWLFFYyKqaSwVyej\",\"midi\":60,\"octave\":4,\"fraction\":null}}]}]}]}}","id":"0"}

nice!
so it just recurses?

Yes. Which would be problematic for circular structures, but that is not relevant for my use case.