3.1 The simplest join — id-match (Person → HubSpot contact by email)
MATCH (p:Person {name:'Ada Lovelace'})-[:HAS_HUBSPOT_CONTACT]->(c:HubSpotContact)RETURN c.company, c.jobtitleMeans: “What does HubSpot say about Ada?”
How it executes:
- Probe — bind
p: find the persistedPersonnamed Ada (scoped to you), read herprimaryEmail— sayada@example.com. That email is the key. - Fetch — call the
contactsByEmailproducer once with['ada@example.com']. It returns Ada’s HubSpot contact record{ email, company, jobtitle, … }. - Materialize — make a transient
(:HubSpotContact:Virtual { … })and an edge(ada)-[:HAS_HUBSPOT_CONTACT]->(c), because the record’semail(recordKeyField) equals Ada’sprimaryEmail(keyField). - Run — your
RETURN c.company, c.jobtitlereads the transient node. Done. - Roll back — the contact node disappears.
Declared: a HubSpotContact type with one virtualJoins entry
{ anchorLabel: Person, relationship: HAS_HUBSPOT_CONTACT, keyField: primaryEmail, recordKeyField: email, producer: contactsByEmail },
and a producers/ entry contactsByEmail (kind: remote).
This is the id-match shape: the anchor is a real domain node and the join links by a shared value (email). Contrast with the bridge shape (§3.2), where the anchor is itself an external identity node.

