The role of elementIds and key properties

This feature has been released as a public beta in AuraDB Enterprise October Release and Neo4j Enterprise Edition 5.13 and breaking changes are likely to be introduced before it is made generally available (GA).

Neo4j internally identifies nodes and relationships by their elementId. Although these internal identifiers are always part of the generated change events, they are not safe to track data out of the scope of a single transaction. You should thus define logical/business keys to your graph entities in the form of node key constraints and relationships key constraints. Properties with node and relationship key constraints are always included in change events as top-level objects.

If key constraints are defined on graph entities, all of the participating property values are captured as event.keys field on node changes and relationship changes. Furthermore, a relationship change also includes the corresponding participating key property values for the relationship’s start and end nodes.

If the node or relationship key constraints are added after a change event is captured, the previously captured change events are not updated with key information retroactively. The key properties are captured from the point the corresponding constraints are created.

Node keys

Assume the following constraints are in place for Person and Employee labels.

Node key constraints for Person label
CREATE CONSTRAINT person_names_key IF NOT EXISTS FOR (n:Person) REQUIRE (n.firstName, n.lastName) IS NODE KEY;
CREATE CONSTRAINT person_ssn_key IF NOT EXISTS FOR (n:Person) REQUIRE (n.ssn) IS NODE KEY;
Node key constraint for Employee label
CREATE CONSTRAINT employee_key IF NOT EXISTS FOR (n:Employee) REQUIRE (n.id) IS NODE KEY

Whenever a change happens on a node with Person label with firstName, lastName and ssn properties set as john, doe and X123456 respectively, the change event includes the following key information.

{
    //...
    "event": {
        "keys": {
            "Person": [
                {
                    "firstName": "john",
                    "lastName": "doe"
                },
                {
                    "ssn": "X123456"
                }
            ]
        }
    }
    //...
}

Likewise, whenever a change happens on a node with Employee label with id property set as 1001, the change event includes the following key information.

{
    //...
    "event": {
        "keys": {
            "Employee": [
                {
                    "id": 1001
                }
            ]
        }
    }
    //...
}

If, on the other hand, the changed node has both Person and Employee labels with firstName, lastName, ssn and id properties set as john, doe, Y654321 and 1001 respectively, the change event includes the following key information.

{
    //...
    "event": {
        "keys": {
            "Person": [
                {
                    "firstName": "john",
                    "lastName": "doe"
                },
                {
                    "ssn": "Y654321"
                }
            ],
            "Employee": [
                {
                    "id": 1001
                }
            ]
        }
    }
    //...
}

For a full description of change record schema, see Format of change events.

Although the preferred way of specifying node key properties is creating a node key constraint, the same can be achieved using both unique node property and node property existence constraints on the same set of properties.
For Change Data Capture, it doesn’t matter whether the constraints were created in one way or the other.

CREATE CONSTRAINT person_firstName_exists IF NOT EXISTS FOR (n:Person) REQUIRE n.firstName IS NOT NULL;
CREATE CONSTRAINT person_lastName_exists IF NOT EXISTS FOR (n:Person) REQUIRE n.lastName IS NOT NULL;
CREATE CONSTRAINT person_unique IF NOT EXISTS FOR (n:Person) REQUIRE (n.firstName, n.lastName) IS UNIQUE;

For details about constraints and syntax of related commands, see Cypher Manual → Constraints.

Relationship keys

Assume the following constraints are in place for MARRIED_TO relationship type.

Relationship key for MARRIED_TO type
CREATE CONSTRAINT married_to_register_key IF NOT EXISTS FOR ()-[r:MARRIED_TO]-() REQUIRE (r.registerId) IS RELATIONSHIP KEY;
CREATE CONSTRAINT married_to_official_key IF NOT EXISTS FOR ()-[r:MARRIED_TO]-() REQUIRE (r.official) IS RELATIONSHIP KEY;

Whenever a change happens on a relationship of type MARRIED_TO with registerId and official properties set as 1125 and Alice Roberts respectively, the change event includes the following key information.

{
    //...
    "event": {
        "keys": [
            {
                "registerId": 1125
            },
            {
                "official": "Alice Roberts"
            }
        ]
    }
    //...
}

If the relationship’s start and end nodes correspond to nodes with node key constraint, those property values are also included in the change event.

{
    //...
    "event": {
        "start": {
            "elementId": "<element-id>",
            "labels": ["Person"],
            "keys": {
                "Person": [
                    {
                        "firstName": "john",
                        "lastName": "doe"
                    }
                ]
            }
        },
        "end": {
            "elementId": "<element-id>",
            "labels": ["Person"],
            "keys": {
                "Person": [
                    {
                        "firstName": "mary",
                        "lastName": "doe"
                    }
                ]
            }
        },
        "keys": [
            {
                "registerId": 1125
            },
            {
                "official": "Alice Roberts"
            }
        ]
    }
    //...
}

For a full description of change record schema, see Format of change events.

Although the preferred way of specifying relationship key properties is creating relationship key constraint, the same can be achieved using both unique relationship property and relationship property existence constraints on the same set of properties.
For Change Data Capture, it doesn’t matter whether the constraints were created in one way or the other.

CREATE CONSTRAINT married_to_registerId_exists IF NOT EXISTS FOR ()-[r:MARRIED_TO]-() REQUIRE (r.registerId) IS NOT NULL;
CREATE CONSTRAINT married_to_registerId_unique IF NOT EXISTS FOR ()-[r:MARRIED_TO]-() REQUIRE (r.registerId) IS UNIQUE;

For details about constraints and syntax of related commands, see Cypher Manual → Constraints.