Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Constraints

patinaDB supports uniqueness, existence (NOT NULL), node-key, and property-type (IS :: <TYPE>) constraints on nodes, plus existence, property-type, uniqueness, and relationship-key constraints on relationships, with Neo4j-5 (and legacy Neo4j-4) DDL — all four Neo4j constraint kinds, on both nodes and relationships. Constraints are enforced at write time, replicate across a cluster, and are carried in backups and Raft snapshots.

Creating constraints

CREATE CONSTRAINT [name] [IF NOT EXISTS]
FOR (n:Label) REQUIRE n.prop IS UNIQUE

Node kinds:

KindDDLEnforces
UniquenessREQUIRE n.prop IS UNIQUENo two :Label nodes share a non-null value for prop.
ExistenceREQUIRE n.prop IS NOT NULLEvery :Label node has prop set (non-null).
Node keyREQUIRE (n.p1, n.p2) IS NODE KEYThe tuple is present on every :Label node and unique (composite unique + existence).
Property typeREQUIRE n.prop IS :: INTEGERWhen present, prop’s value is of the declared type (does not require presence).

Relationship kinds (FOR ()-[r:TYPE]-(), any direction):

KindDDLEnforces
ExistenceREQUIRE r.prop IS NOT NULLEvery :TYPE relationship has prop set (non-null).
Property typeREQUIRE r.prop IS :: INTEGERWhen present, prop’s value is of the declared type.
UniquenessREQUIRE r.prop IS UNIQUENo two :TYPE relationships share a non-null value for prop.
Relationship keyREQUIRE (r.p1, r.p2) IS [REL|RELATIONSHIP] KEYThe tuple is present on every :TYPE relationship and unique.

Relationship uniqueness/key is index-backed by the existing edge-property value index (O(log N + matches)), not a full scan of the relationship type.

The legacy Neo4j-4 forms are also accepted: CREATE CONSTRAINT … ON (n:Label) ASSERT n.prop IS UNIQUE and the shorthand ON :Label(prop). The property-type predicate also accepts the IS TYPED <TYPE> synonym of IS :: <TYPE>.

Property types

IS :: <TYPE> (and IS TYPED <TYPE>) accepts: BOOLEAN, STRING, INTEGER, FLOAT, POINT, DATE, LOCAL TIME, ZONED TIME, LOCAL DATETIME, ZONED DATETIME, DURATION, and LIST (BOOL/INT are accepted as aliases). A missing or null property is allowed — a type constraint restricts a present value’s type only; combine it with an existence constraint if you also need the property present. Element-type refinement (LIST<INTEGER NOT NULL>) is not yet supported (bare LIST only).

CREATE CONSTRAINT person_email FOR (p:Person) REQUIRE p.email IS UNIQUE
-- status: 'Constraint created on :Person(email) IS UNIQUE'

CREATE CONSTRAINT emp_key FOR (e:Employee) REQUIRE (e.dept, e.num) IS NODE KEY
-- status: 'Constraint created on :Employee(dept, num) IS NODE KEY'

The optional name identifies the constraint for DROP and SHOW. IF NOT EXISTS makes creation idempotent.

Creating over existing data

CREATE CONSTRAINT first validates the current graph. If the existing data already violates the constraint (a duplicate value, or a missing required property), the statement fails and no constraint is registered — fix the data and re-run.

Enforcement semantics

Enforcement runs at write time on CREATE, MERGE, and SET / REMOVE — including a SET / REMOVE inside a FOREACH body, which is enforced identically to a top-level write:

CREATE (:Person {email: 'ada@x.io', name: 'Ada'})

CREATE (:Person {email: 'ada@x.io', name: 'Ada2'})
-- error: Unique constraint violation on :Person(email): value already exists
  • UNIQUE is NULL-exempt. A null (or absent) value is not constrained — many nodes (or relationships) may omit prop. Only two concrete equal values collide. (Use a node-key/relationship-key or a separate existence constraint if you also need the property present.)
  • 1 and 1.0 are the same value. Uniqueness treats an integer and a mathematically-equal float as identical (matching Neo4j), so under UNIQUE(k) a stored Integer 1 collides with a CREATE/SET of 1.0, and vice-versa. This holds for node UNIQUE, relationship UNIQUE, and every column of a composite NODE KEY / REL KEY, on both the single-writer path and concurrent transactions. The fold is exact — a float merges with an integer only when it is finite, whole, and round-trips bit-for-bit — so two genuinely different values never collide, and NULL stays exempt as above. See Cypher → numeric equivalence for the full rule. (Very large exact-integer float values — roughly ≥ 2^59, e.g. 1e18 — are still enforced correctly, but fall back to a scan instead of an index seek, so those checks can be slower.)
  • Existence and node-key/relationship-key reject a missing/null required property — including a CREATE/MERGE that omits it, a SET n.prop = null, or a REMOVE n.prop. This holds for both node existence and relationship existence (checked at edge create, SET r.prop = null, SET r =/+=, and REMOVE r.prop).
  • Property-type rejects a present wrong-typed value on CREATE/MERGE/SET (node or relationship). A missing/null value is allowed.
  • The uniqueness check is an O(log N + matches) seek of the label-scoped value index (node) or the edge-property value index (relationship), so it is cheap even on a large label/type.
  • Node-key uniqueness is served by an automatically registered backing compound index over the key tuple — you do not create it separately. Relationship-key uniqueness is served the same way over the edge-property value index (no separate index to create).

Inspecting and dropping

SHOW CONSTRAINTS
constraints: ['person_email: :Person(email) IS UNIQUE', 'emp_key: :Employee(dept, num) IS NODE KEY', 'person_name: :Person(name) IS NOT NULL']

Relationship constraints look the same, scoped by type instead of label, e.g. REQUIRE (r.a, r.b) IS REL KEY FOR ()-[r:WORKS_AT]-().

DROP CONSTRAINT person_email [IF EXISTS]

Replication and durability

  • Cluster replication is automatic. Constraint DDL replicates through the Raft log and re-runs deterministically on every node (the same IndexDdl path that index DDL uses).
  • Raft snapshots carry the defs. A follower that bootstraps purely from a streamed snapshot (after the DDL log is purged) still enforces every constraint.
  • Portable backups carry the defs too. GET /mgmt/snapshot / POST /mgmt/restore round-trip UNIQUE, existence, and node-key defs (alongside vector indexes, tags, and RBAC users) — restore re-issues them as CREATE CONSTRAINT … IF NOT EXISTS. See the REST API.

Not supported

  • LIST<T> element-type refinement — only bare LIST is accepted.
  • Bare composite UNIQUE without existence — use IS NODE KEY / IS [REL|RELATIONSHIP] KEY for a multi-property key (which also requires presence).

See Limitations for the full schema-feature scope, and the Data Model for how properties and the value index work.