Chapter 09 · Constraints and Indexes: Range, Text, Point, Token, Full-Text, and Schema Enforcement

Range, Text, Point, and Token Lookup Indexes: Supported Predicates and Workload Fit

Match each AtlasMart predicate to the index family that can actually serve it.

Intermediate130–160 minutesSearch-performance index evidence labNeo4j 2026.07.1 Community · Cypher 25Last reviewed: September 2026

Learning outcomes

Once integrity is explicit, AtlasMart needs predictable starting points for reads: price filters, product-name substring search, nearby-store/location queries, and simple label/type scans. Search-performance indexes are access paths selected by the planner; choosing the wrong family can add write/storage cost without serving the predicate that motivated it.

01

Map range, text, point and token lookup indexes to the predicates they can solve.

02

Distinguish property indexes from the default label/type token lookup indexes.

03

Use EXPLAIN/PROFILE to verify planner access-path choice instead of assuming it.

04

Create node and relationship indexes only for evidenced query patterns.

05

Identify over-indexing and unsupported predicate/index combinations.

Chapter 09 baseline · reviewed 9 September 2026

The mandatory lab continues Neo4j Community 2026.07.1, database neo4j, explicit CYPHER 25 for version-sensitive examples, authentication enabled, no mandatory APOC/GDS plugin, and the AtlasMart identifiers/model established in Chapters 01–08. Neo4j 5.26.30 remains the LTS comparison line. Community currently supports node and relationship property-uniqueness constraints. Property-existence, property-type and key constraints—and Cypher 25 graph types—are Enterprise-only; Enterprise examples in this chapter are optional and are never presented as Community output.

Evidence and safety note

This generation environment does not run Neo4j or Docker. Commands were checked against current official documentation but were not executed here. Expected plans, counts, index states and scores are therefore described by invariant rather than fabricated as captured output. All disposable data uses labTag='ch09'; all disposable schema objects are named ch09_*. Never drop an index/constraint merely because its name looks similar to a lab object—verify SHOW INDEXES/SHOW CONSTRAINTS first.

1. Four search-performance index families solve different access problems

Index family Primary workload Node/relationship Important boundary
Range Equality, inequalities, many other property predicates, ordering Both Default search-performance index; not specialized substring search
Text STRING predicates, especially CONTAINS / ENDS WITH Both Single property only; string-focused
Point Spatial distance and bounding-box predicates on POINT Both Single point property; not a general numeric range index
Token lookup Node labels or relationship types Separate node + relationship indexes No property predicates; two lookup indexes normally exist by default

Search-performance indexes are considered automatically by the cost planner. An index definition is therefore only a candidate access path; the final plan still depends on the complete query, statistics and available alternatives.

2. Build a deterministic AtlasMart search fixture

Cypher · fixture
CYPHER 25CREATE CONSTRAINT product_id IF NOT EXISTSFOR (p:Product) REQUIRE p.productId IS UNIQUE;CREATE CONSTRAINT category_id IF NOT EXISTSFOR (c:Category) REQUIRE c.categoryId IS UNIQUE;CREATE CONSTRAINT order_id IF NOT EXISTSFOR (o:Order) REQUIRE o.orderId IS UNIQUE;CREATE CONSTRAINT ch09_contains_line_id_unique IF NOT EXISTSFOR ()-[r:CONTAINS]-() REQUIRE r.lineId IS UNIQUE;CYPHER 25MERGE (c1:Category {categoryId:'CAT-9001'}) SET c1.name='Field Electronics',c1.labTag='ch09'MERGE (c2:Category {categoryId:'CAT-9002'}) SET c2.name='Power',c2.labTag='ch09'MERGE (p1:Product {productId:'P-9001'}) SET p1.name='Trail Camera Pro',p1.description='weatherproof wildlife camera with night vision',p1.categoryCode='CAM',p1.price=189.0,p1.location=point({latitude:40.7128,longitude:-74.0060}),p1.labTag='ch09'MERGE (p2:Product {productId:'P-9002'}) SET p2.name='Trail Camera Mini',p2.description='compact wildlife camera',p2.categoryCode='CAM',p2.price=99.0,p2.location=point({latitude:40.7138,longitude:-74.0040}),p2.labTag='ch09'MERGE (p3:Product {productId:'P-9003'}) SET p3.name='Field Battery',p3.description='high capacity outdoor battery pack',p3.categoryCode='PWR',p3.price=49.0,p3.location=point({latitude:40.7100,longitude:-74.0100}),p3.labTag='ch09'MERGE (p1)-[:IN_CATEGORY]->(c1)MERGE (p2)-[:IN_CATEGORY]->(c1)MERGE (p3)-[:IN_CATEGORY]->(c2)MERGE (o:Order {orderId:'O-9001'}) SET o.labTag='ch09',o.status='PAID'MERGE (o)-[r1:CONTAINS]->(p1) SET r1.lineId='L-9001',r1.quantity=1,r1.unitPrice=189.0,r1.labTag='ch09'MERGE (o)-[r2:CONTAINS]->(p3) SET r2.lineId='L-9002',r2.quantity=2,r2.unitPrice=49.0,r2.labTag='ch09';
Cypher · inventory default and lab indexes before changes
CYPHER 25SHOW INDEXESYIELD name,type,state,entityType,labelsOrTypes,properties,owningConstraintRETURN name,type,state,entityType,labelsOrTypes,properties,owningConstraintORDER BY type,name;

3. Range and relationship range indexes

AtlasMart filters product prices and occasionally starts from an order-line unitPrice. Both are scalar-property access patterns, so range indexes are appropriate candidates. The relationship example matters because indexable graph state is not limited to nodes.

Cypher · range indexes
CYPHER 25CREATE RANGE INDEX ch09_product_price_range IF NOT EXISTSFOR (p:Product) ON (p.price);CREATE RANGE INDEX ch09_contains_unitprice_range IF NOT EXISTSFOR ()-[r:CONTAINS]-() ON (r.unitPrice);CALL db.awaitIndexes(300);
Cypher · inspect planner candidates
CYPHER 25EXPLAIN MATCH (p:Product) WHERE p.price >= 100 RETURN p.productId,p.price;EXPLAIN MATCH ()-[r:CONTAINS]-() WHERE r.unitPrice >= 100 RETURN r.lineId,r.unitPrice;

Do not hard-code an operator name as a permanent acceptance criterion. Verify that the plan uses an appropriate index-backed access path and compare actual rows/database hits with PROFILE on the target release and data distribution.

4. Text indexes serve substring-oriented string predicates

Cypher · text index and representative predicate
CYPHER 25CREATE TEXT INDEX ch09_product_name_text IF NOT EXISTSFOR (p:Product) ON (p.name);CALL db.awaitIndex('ch09_product_name_text',300);EXPLAIN MATCH (p:Product) WHERE p.name CONTAINS 'Camera' RETURN p.productId,p.name;EXPLAIN MATCH (p:Product) WHERE p.name ENDS WITH 'Mini' RETURN p.productId,p.name;

If both a range and text index exist on the same string property, the planner normally prefers the text index for CONTAINS/ENDS WITH and the range index for other supported predicates. That is a workload distinction, not a declaration that one index family is globally “faster.”

5. Point and token lookup indexes answer different dimensions

Cypher · point index
CYPHER 25CREATE POINT INDEX ch09_product_location_point IF NOT EXISTSFOR (p:Product) ON (p.location);CALL db.awaitIndex('ch09_product_location_point',300);EXPLAIN MATCH (p:Product)WHERE point.distance(p.location,point({latitude:40.7128,longitude:-74.0060})) < 1000RETURN p.productId,p.name;
Cypher · token lookup evidence
CYPHER 25SHOW LOOKUP INDEXESYIELD name,state,entityType,labelsOrTypes,propertiesRETURN name,state,entityType,labelsOrTypes,properties;EXPLAIN MATCH (p:Product) RETURN count(p);EXPLAIN MATCH ()-[r:CONTAINS]->() RETURN count(r);

Token lookup indexes solve the label/type token lookup; they cannot solve p.price, p.name or any other property predicate. Do not drop the default lookup indexes as a “cleanup” experiment in a shared database.

6. Deliberately wrong: create an index for every visible property

Over-indexing increases schema-build work, storage and mutation overhead. The repair is query-first: record a representative query, its predicate shape, cardinality/selectivity, current plan and latency distribution, then create the smallest access path that changes evidence in the desired direction.

Check your understanding

  1. Which index family is specialized for CONTAINS and ENDS WITH on strings?
  2. What do token lookup indexes index?
  3. Can relationships have range/text/point indexes?
  4. Why use EXPLAIN before PROFILE?
  5. Why is “index every property” unsafe advice?
Review the answers

1. A text index.

2. Node labels and relationship types, not properties.

3. Yes, current search-performance index syntax supports relationship properties.

4. EXPLAIN shows the planned access path without executing; PROFILE executes and collects runtime evidence.

5. Indexes consume storage/build resources and add write maintenance while many may never serve a representative query.

Summary and next step

Index type follows predicate type. Next, the chapter examines composite order, selectivity and the lifecycle state that determines whether an index is actually usable.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.