Skip to main content

Constraints

LoraDB remains schema-free by default: labels, relationship types, and property keys appear when you write them. Constraints are optional catalog entries that add validation for selected labels or relationship types.

Constraints are checked in two places:

  • when the constraint is created, existing matching data must already satisfy it;
  • after creation, writes that would violate the constraint fail.

Create Constraints​

Every constraint needs a name:

CREATE CONSTRAINT user_email
FOR (u:User)
REQUIRE u.email IS UNIQUE;

CREATE CONSTRAINT author_name
FOR (a:Author)
REQUIRE a.name IS NOT NULL;

CREATE CONSTRAINT actor_name
FOR (a:Actor)
REQUIRE (a.first, a.last) IS NODE KEY;

CREATE CONSTRAINT owns_id
FOR ()-[o:OWNS]-()
REQUIRE o.ownershipId IS RELATIONSHIP KEY;

CREATE CONSTRAINT movie_title
FOR (m:Movie)
REQUIRE m.title IS :: STRING;

Use IF NOT EXISTS to make creation idempotent:

CREATE CONSTRAINT user_email IF NOT EXISTS
FOR (u:User)
REQUIRE u.email IS UNIQUE;

Constraint names may also come from a string parameter:

CREATE CONSTRAINT $name
FOR (u:User)
REQUIRE u.email IS UNIQUE;

Constraint Kinds​

KindSyntaxApplies to
Property uniquenessREQUIRE n.email IS UNIQUENodes or relationships; single or composite
Property existenceREQUIRE n.email IS NOT NULLNodes or relationships; single property
Node keyREQUIRE (n.a, n.b) IS NODE KEYNodes; existence plus uniqueness
Relationship keyREQUIRE r.id IS RELATIONSHIP KEYRelationships; existence plus uniqueness
Property typeREQUIRE n.title IS :: STRINGNodes or relationships; single property

Composite uniqueness and key constraints must wrap the property list in parentheses:

CREATE CONSTRAINT order_line
FOR ()-[r:LINE_ITEM]-()
REQUIRE (r.orderId, r.lineNo) IS UNIQUE;

A uniqueness or key constraint is backed by a RANGE index of the same name. For a key that is only ever looked up by value ({key: $k}, MERGE), OPTIONS { indexProvider: 'hash' } keeps the hash index that enforcement and lookups read and drops the sorted copy of each value: about 110 bytes less per node for a 58-character string key. Range, STARTS WITH and ORDER BY reads on the key still work, by scanning the label.

CREATE CONSTRAINT record_key
FOR (r:Record)
REQUIRE r.key IS UNIQUE
OPTIONS { indexProvider: 'hash' };

'range' (Neo4j's 'range-1.0' is accepted too) is the default. To change the backing, drop the constraint and create it again.

Existence constraints are single-property only:

CREATE CONSTRAINT published_at
FOR (p:Post)
REQUIRE p.publishedAt IS NOT NULL;

Existence is judged on the graph as a statement leaves it, so one statement may add a label and its required property in either order, or replace or remove a required property and set it again. A statement that ends without the property is rejected and changes nothing. Uniqueness and type constraints are checked at each write.

MATCH (d:Draft {id: $id})
SET d:Post, d.publishedAt = datetime();     // ok

MATCH (p:Post {id: $id})
SET p = {id: $id, title: $title},
  p.publishedAt = datetime();             // ok

MATCH (p:Post {id: $id})
SET p = {id: $id}                           // rejected, nothing changes

Property Types​

Property type constraints accept scalar types, lists with non-null elements, vectors, and closed unions:

CREATE CONSTRAINT movie_released
FOR (m:Movie)
REQUIRE m.released IS :: DATE;

CREATE CONSTRAINT article_tags
FOR (a:Article)
REQUIRE a.tags IS :: LIST<STRING NOT NULL>;

CREATE CONSTRAINT doc_embedding
FOR (d:Doc)
REQUIRE d.embedding IS :: VECTOR<FLOAT32>(1536);

CREATE CONSTRAINT tagline
FOR (m:Movie)
REQUIRE m.tagline IS :: STRING | LIST<STRING NOT NULL>;

Supported scalar type names are BOOLEAN, STRING, INTEGER, FLOAT, DATE, LOCAL TIME, ZONED TIME, LOCAL DATETIME, ZONED DATETIME, DURATION, and POINT.

MAP, ANY, and nullable list element types such as LIST<STRING> are rejected for property type constraints. Vector dimensions must be in 1..=4096, and the coordinate type must match the stored vector type.

Inspect Constraints​

SHOW CONSTRAINTS;

Rows contain:

ColumnMeaning
nameConstraint name
typeNODE_PROPERTY_UNIQUENESS, RELATIONSHIP_PROPERTY_UNIQUENESS, NODE_PROPERTY_EXISTENCE, RELATIONSHIP_PROPERTY_EXISTENCE, NODE_KEY, RELATIONSHIP_KEY, NODE_PROPERTY_TYPE, or RELATIONSHIP_PROPERTY_TYPE
entityTypeNODE or RELATIONSHIP
labelsOrTypesLabel or relationship type scope
propertiesConstrained property keys
ownedIndexBacking index name for uniqueness and key constraints; null otherwise
indexProviderrange or hash for uniqueness and key constraints; null otherwise
propertyTypeType expression for property type constraints; null otherwise

SHOW CONSTRAINTS accepts the same YIELD-anchored catalog pipeline as SHOW INDEXES:

SHOW CONSTRAINTS
YIELD name, type
WHERE type = 'NODE_PROPERTY_UNIQUENESS'
RETURN name
ORDER BY name;

Drop Constraints​

DROP CONSTRAINT user_email;
DROP CONSTRAINT maybe_missing IF EXISTS;
DROP CONSTRAINT $name;

Uniqueness and key constraints own a backing RANGE index with the same name. Drop the constraint, not the backing index; direct DROP INDEX on a constraint-owned index is rejected.

Write Enforcement​

Constraints are enforced for matching writes:

CREATE CONSTRAINT user_email
FOR (u:User)
REQUIRE u.email IS UNIQUE;

CREATE (:User {email: 'ada@example.com'});
CREATE (:User {email: 'ada@example.com'}); // rejected

Enforcement covers node and relationship creation, property updates, property replacement through SET n = {...}, property removal, and adding a label that activates a node constraint.

Durability​

Constraint catalog entries are included in snapshots and replayed from WAL records during recovery. Uniqueness and key constraints recreate their backing indexes as part of the catalog state.

Limitations​

  • Constraints are label/type scoped; there is no database-wide uniqueness constraint.
  • Existence constraints are single-property only.
  • Property type constraints do not support MAP, ANY, or nullable list element types.
  • Constraint names are required.
  • Dropping a backing index directly is rejected; use DROP CONSTRAINT.

See Also​