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

Full-Text Search

patinaDB supports user-defined full-text indexes with BM25 ranking, using Neo4j-compatible syntax. You create an index over chosen string properties of a label (nodes) or relationship type (edges), then query it through the full-text procedures, getting back ranked entities and relevance scores.

Creating an index

-- Over node properties
CREATE FULLTEXT INDEX docs FOR (n:Doc) ON EACH [n.title, n.body]

-- Over relationship properties
CREATE FULLTEXT INDEX mentions FOR ()-[r:MENTIONS]-() ON EACH [r.context]

-- With an analyzer
CREATE FULLTEXT INDEX articles FOR (n:Article) ON EACH [n.body]
OPTIONS { indexConfig: { `fulltext.analyzer`: "english" } }
  • Only string properties are indexed (matching Neo4j). Non-string values on the listed properties are ignored.
  • The index is maintained synchronously: it is updated as part of every committed write, so it is never stale on read (no eventual consistency).

Analyzers

The analyzer controls tokenization and stemming:

AnalyzerBehaviour
standardLowercase, tokenize on non-alphanumerics. (Default.)
keywordTreat the whole value as a single token (exact-match indexing).
englishStandard + English Snowball stemming + English stop words.
germanStandard + German Snowball stemming + German stop words.

Managing indexes

SHOW FULLTEXT INDEXES
DROP INDEX docs

DROP INDEX <name> drops a full-text index by name. The Neo4j DROP INDEX ON :Label(prop) form (for ordinary indexes) is a different, unsupported statement and is left to the engine.

Querying

CALL db.index.fulltext.queryNodes('docs', 'graph database')
YIELD node, score
RETURN node, score ORDER BY score DESC

CALL db.index.fulltext.queryRelationships('mentions', '"exact phrase"')
YIELD relationship, score
RETURN relationship, score

The alias patinadb.fulltext.queryNodes / …queryRelationships is equivalent. Results are ranked by BM25 (k1 = 1.2, b = 0.75).

Query syntax (Lucene subset)

FormExampleMeaning
TermgraphDocuments containing the term.
Implicit ORgraph databaseAdjacent terms are OR’d.
AND / OR / NOTgraph AND NOT sqlBoolean operators; AND binds tighter than OR.
Phrase"graph database"Terms in that exact order (consecutive positions).
Prefixdata*Terms starting with data.
Fuzzydatabse~, db~2Edit-distance match (default / explicit distance).
Boostgraph^3 databaseMultiply a term’s contribution to the score.
Fieldtitle:graphRestrict a term to one indexed property.
Grouping(graph OR sql) AND dbParenthesised sub-expressions.

Prefix and fuzzy queries expand against the index dictionary and are capped at 256 expansions per term to bound cost.

In the server

CREATE FULLTEXT INDEX / DROP INDEX are replicated as Raft control commands (like CREATE DATABASE): the definition propagates to every node and each node builds the index from its own copy of the graph. Index definitions are carried in Raft snapshots and rebuilt on snapshot install, so a node that joins or restarts ends up with the same indexes. Query procedures read the local node’s index.

This works end-to-end over Bolt, including the Neo4j Browser: create an index and run queryNodes, and matching nodes come back as graph nodes with scores.

Limitations

  • No phrase slop / proximity ("a b"~3) — phrases must be exactly consecutive.
  • No highlighting / snippet extraction.
  • No numeric or range queries inside the full-text string (full-text indexes cover string properties only).
  • Postings are updated read-modify-write per document with no segment merging, so very high write throughput on a large indexed corpus is not the design target. See Limitations.