AgensGraph Logo

AgensGraph Manual

  • Release notes
    • Release notes 2.17.0
  • Quick Guide
    • Installation
      • Installing AgensGraph on Linux
      • Post-Installation Setup and Configuration
      • Quick Start with Docker
      • Configuring Server Parameters
    • Data Model
      • Property Graph Model
      • Detailed Description
    • AgensGraph Query
      • Graph Query
      • Hybrid Query
    • Graph Data Import
      • Example
      • Indexes with Data Import
    • Vector Search
      • Overview
      • Installing the extension
      • Example data
      • Similarity search
      • Indexing with HNSW
      • GraphRAG: combining vector search with graph traversal
    • Full Text Search
      • Overview
      • Example data
      • Searching a text property
      • Ranking results
      • When the exact word matters
      • Indexing with GIN
      • GraphRAG: combining full-text search with graph traversal
    • Hybrid Search
      • Overview
      • Combining two rankings: Reciprocal Rank Fusion
      • Example data
      • The hybrid query
      • Indexing
      • Tuning
      • GraphRAG: combining hybrid search with graph traversal
    • Tools
      • Command Line Interface Tool
    • Drivers
  • Cypher Manual
    • Getting Started
      • Creating Graph
      • Querying Graph
      • Manipulating Graph
    • Syntax
      • Pattern
    • Clauses
      • RETURN
      • ORDER BY
      • LIMIT
      • SKIP
      • WITH
      • UNION
    • Functions
      • Aggregation
      • Predicate
      • Scalar
      • List
      • Path
      • Math
      • String
  • Operation Manual
    • Deployment
      • System Requirements
      • Pre-install tasks
      • Single instance install
      • Operations
    • Architecture
      • Process structure
    • Security
      • Client Authentication
    • Backup & Recovery
      • Backup
      • Recovery
    • Configuration
      • Configuration Settings Reference
      • File Locations
      • Resource Consumption
      • Write Ahead Log
      • Query Planning
      • Error Reporting and Logging
      • Version and Platform Compatibility
    • Tools
      • Client Tool
      • Server Tool
    • Appendix
      • System Catalogs
      • System Views
  • Developer Manual
    • Graph Database Concepts
      • Vertices
      • Edges
      • Properties
      • Labels
      • Traversal
      • Paths
    • Get started with Cypher
      • Creating Graphs
      • Creating Users
      • Creating Labels
      • Creating Vertices and Edges
    • Functions
      • Aggregate functions
      • Predicate functions
      • Scalar functions
      • List functions
      • Mathematical functions
      • String functions
    • SQL Language
      • Introduction
    • Data Type
      • Numeric Types
      • Character Types
      • Date/Time Types
      • Boolean Type
      • Geometric Types
      • XML Type
      • JSON Types
      • Arrays
      • Range Types
      • User-defined Type
    • Functions
      • Comparison functions
      • Mathematical functions
      • String functions
      • Binary String functions
      • Date Type Formatting functions
      • Date/Time functions
      • Enum Support functions
      • Geometric Functions
      • Network Address Functions
      • Text Search Functions
      • JSON Functions
      • Sequence Manipulation Functions
      • Array Functions
      • Range Functions and Operators
      • Aggregate Functions
      • Window Functions
      • System Information Functions
      • System Administration Functions
      • User-defined function
    • SQL and Cypher
      • Cypher in SQL
      • SQL in Cypher
    • Vector Search
      • Overview
      • Installing the extension
      • Example data
      • Similarity search
      • Indexing with HNSW
      • GraphRAG: combining vector search with graph traversal
    • Full Text Search
      • Overview
      • Example data
      • Searching a text property
      • Ranking results
      • When the exact word matters
      • Indexing with GIN
      • GraphRAG: combining full-text search with graph traversal
    • Hybrid Search
      • Overview
      • Combining two rankings: Reciprocal Rank Fusion
      • Example data
      • The hybrid query
      • Indexing
      • Tuning
      • GraphRAG: combining hybrid search with graph traversal
    • Drivers
    • Procedural language
      • Installation
      • PL/pgSQL
      • PL/Python
    • Appendix
      • Error Codes
      • Terminologies
      • FAQ
    • Graph Meta
      • Introduction
      • Installation
      • Functions
    • AI Integration
  • Upgrade Guide
    • Using pg_upgrade
      • How pg_upgrade works
      • Before you begin
      • Step 1 — Stop the old server
      • Step 2 — Initialize the new cluster
      • Step 3 — Check compatibility
      • Step 4 — Run the upgrade
      • Step 5 — Start the new server
      • Step 6 — Post-upgrade tasks
      • Rolling back
      • pg_upgrade options reference
    • Demo
AgensGraph
  • Quick Guide
  • Full Text Search

Full Text Search

Overview

AgensGraph is built on PostgreSQL and inherits its full-text search engine, so you can run linguistic keyword search directly over text stored in graph properties. Full-text search normalizes documents and queries into lexemes (stemming, stop-word removal, case folding), which makes it well suited to lexical retrieval — matching the actual words a user typed. It is the natural complement to vector search, which matches by meaning; combining the two is covered in Hybrid Search. All three pages use the same movies, so the results can be compared directly.

A graph property is stored as jsonb, so a text property is cast to text (with ::text) before being converted to a tsvector.

Example data

The examples use a catalogue of films, each with a plot text property (the same movies as in Vector Search, which also embeds the plots):

CREATE GRAPH moviekb;
SET graph_path = moviekb;
CREATE VLABEL movie;

CREATE (:movie {title: 'The Matrix', year: 1999, genre: 'Action', plot: 'A computer hacker learns about the true nature of reality and joins a rebellion to free humanity from a simulated world controlled by machines.', embedding: [-0.07594558, 0.04081754, 0.29592122, -0.11921061]}),
       (:movie {title: 'The Matrix Reloaded', year: 2003, genre: 'Action', plot: 'The rebels continue their fight against the machines, uncovering deeper truths about the Matrix and the nature of their mission.', embedding: [0.30228977, -0.22839354, 0.35070436, 0.01262819]}),
       (:movie {title: 'The Matrix Revolutions', year: 2003, genre: 'Action', plot: 'The final battle between humans and machines reaches its climax as the fate of both worlds hangs in the balance.', embedding: [0.12240622, -0.29752459, 0.22620453, 0.24454723]}),
       (:movie {title: 'The Matrix Resurrections', year: 2021, genre: 'Action', plot: 'Neo returns to a new version of the Matrix and must once again fight to save the people from the control of the machines.', embedding: [0.34717246, -0.13820869, 0.29214213, 0.08090488]}),
       (:movie {title: 'Inception', year: 2010, genre: 'Sci-Fi', plot: 'A skilled thief is given a chance at redemption if he can successfully perform an inception: planting an idea into someone''s subconscious.', embedding: [0.03923657, 0.39284106, -0.20927092, -0.17770818]}),
       (:movie {title: 'Interstellar', year: 2014, genre: 'Sci-Fi', plot: 'A group of explorers travel through a wormhole in space in an attempt to ensure humanity''s survival.', embedding: [-0.29302418, -0.39615033, -0.23393948, -0.09601383]}),
       (:movie {title: 'Avatar', year: 2009, genre: 'Sci-Fi', plot: 'A paraplegic Marine is sent to the moon Pandora, where he becomes torn between following orders and protecting the world he feels is his home.', embedding: [-0.13663386, 0.00635589, -0.03038832, -0.08252723]}),
       (:movie {title: 'Blade Runner', year: 1982, genre: 'Sci-Fi', plot: 'A blade runner must pursue and terminate four replicants who have stolen a ship in space and returned to Earth.', embedding: [0.27215557, -0.1479577, -0.09972772, -0.08234394]}),
       (:movie {title: 'Blade Runner 2049', year: 2017, genre: 'Sci-Fi', plot: 'A new blade runner unearths a long-buried secret that has the potential to plunge what''s left of society into chaos.', embedding: [0.21560573, -0.07505179, -0.01331814, 0.13403069]}),
       (:movie {title: 'Minority Report', year: 2002, genre: 'Sci-Fi', plot: 'In a future where a special police unit can arrest murderers before they commit their crimes, a top officer is accused of a future murder.', embedding: [0.24008012, 0.44954908, -0.30905488, 0.15195407]}),
       (:movie {title: 'Total Recall', year: 1990, genre: 'Sci-Fi', plot: 'A construction worker discovers that his memories have been implanted and becomes embroiled in a conspiracy on Mars.', embedding: [-0.17471036, 0.14695261, -0.06272433, -0.21795064]}),
       (:movie {title: 'Elysium', year: 2013, genre: 'Sci-Fi', plot: 'In a future where the rich live on a luxurious space station while the rest of humanity lives in squalor, a man fights to bring equality.', embedding: [-0.33280967, 0.07733926, 0.11015328, 0.53382836]}),
       (:movie {title: 'Gattaca', year: 1997, genre: 'Sci-Fi', plot: 'In a future where genetic engineering determines social class, a man defies his fate to achieve his dreams.', embedding: [-0.21629286, 0.31114665, 0.08303899, 0.46199759]}),
       (:movie {title: 'The Fifth Element', year: 1997, genre: 'Sci-Fi', plot: 'In a futuristic world, a cab driver becomes the key to saving humanity from an impending cosmic threat.', embedding: [-0.11528205, -0.0208782, -0.0735215, 0.14327449]}),
       (:movie {title: 'The Terminator', year: 1984, genre: 'Action', plot: 'A cyborg assassin is sent back in time to kill the mother of the future resistance leader.', embedding: [0.33666933, 0.18040994, -0.01075103, -0.11117851]}),
       (:movie {title: 'Terminator 2: Judgment Day', year: 1991, genre: 'Action', plot: 'A reprogrammed Terminator is sent to protect the future leader of the human resistance from a more advanced Terminator.', embedding: [0.34698868, 0.06439331, 0.06232323, -0.19534876]}),
       (:movie {title: 'Jurassic Park', year: 1993, genre: 'Adventure', plot: 'Scientists clone dinosaurs to create a theme park, but things go awry when the creatures escape.', embedding: [0.01794725, -0.11434246, -0.46831815, -0.01049593]}),
       (:movie {title: 'The Avengers', year: 2012, genre: 'Action', plot: 'Superheroes assemble to face a global threat from an alien invasion led by Loki.', embedding: [0.00546514, -0.37005171, -0.42612838, 0.07968612]});

Searching a text property

Convert the property to a tsvector with to_tsvector, parse the user’s input with one of the *_to_tsquery functions, and match them with the @@ operator. websearch_to_tsquery accepts a forgiving, web-style syntax (quoted phrases, OR, and - to exclude a term), which makes it a good default for user input.

MATCH (m:movie)
WHERE to_tsvector('english', m.plot::text) @@ websearch_to_tsquery('english', 'machines')
RETURN m.title AS title;
           title
----------------------------
 "The Matrix"
 "The Matrix Reloaded"
 "The Matrix Revolutions"
 "The Matrix Resurrections"
(4 rows)

The query is matched against lexemes, not raw text: machines is reduced to the stem machin, so the singular machine would match exactly the same rows. Note also what is not returned — Total Recall and The Terminator are thematically about machine-dominated futures, but their plots never use the word, so keyword search cannot find them. That recall gap is exactly where vector search helps.

Other query parsers are available depending on the input you expect:

Function

Input style

websearch_to_tsquery

Web-style: machines "virtual reality" -documentary

plainto_tsquery

Plain text, all terms AND-ed together

to_tsquery

Explicit operators: machines & reality

Ranking results

ts_rank scores how well each document matches the query, so results can be ordered by relevance. A plot that matches more of the query terms ranks higher:

MATCH (m:movie)
WHERE to_tsvector('english', m.plot::text) @@ websearch_to_tsquery('english', 'machines OR reality')
RETURN m.title AS title,
       ts_rank(to_tsvector('english', m.plot::text),
               websearch_to_tsquery('english', 'machines OR reality')) AS rank
ORDER BY rank DESC;
           title            |    rank
----------------------------+-------------
 "The Matrix"               |  0.06079271
 "The Matrix Reloaded"      | 0.030396355
 "The Matrix Revolutions"   | 0.030396355
 "The Matrix Resurrections" | 0.030396355
(4 rows)

The Matrix ranks above its sequels because its plot contains both machines and reality, while the sequels match only machines.

When the exact word matters

Full-text search is the right tool when the query is a specific term — a name, a code, a rare keyword — that must match exactly. Asked for the film about dinosaurs, it returns precisely one:

MATCH (m:movie)
WHERE to_tsvector('english', m.plot::text) @@ websearch_to_tsquery('english', 'dinosaurs')
RETURN m.title AS title;
      title
-----------------
 "Jurassic Park"
(1 row)

Vector search has no notion of the literal word dinosaurs. Asked for the films most similar to Jurassic Park, it returns The Avengers as the nearest neighbor — both are large-scale spectacle blockbusters, a reasonable guess by meaning, but not what someone typing “dinosaurs” wants:

MATCH (m:movie), (q:movie {title: 'Jurassic Park'})
RETURN m.title AS title
ORDER BY m.embedding::vector(4) <=> q.embedding::vector(4)
LIMIT 3;
      title
-----------------
 "Jurassic Park"
 "The Avengers"
 "Interstellar"
(3 rows)

So the two methods have opposite blind spots: full-text search is precise but misses synonyms and intent, while vector search captures meaning but drifts on exact terms. Hybrid Search runs both and fuses the results to get the strengths of each.

Indexing with GIN

Recomputing to_tsvector for every row on each search does not scale. Create a GIN index on the same to_tsvector expression so matches are found through the index. Because the indexed value is an expression over a property, use a property index and wrap the expression in parentheses:

CREATE PROPERTY INDEX ON movie
    USING gin ((to_tsvector('english', plot::text)));

The search then uses the index instead of scanning every row:

EXPLAIN
MATCH (m:movie)
WHERE to_tsvector('english', m.plot::text) @@ websearch_to_tsquery('english', 'machines')
RETURN m.title;
 Bitmap Heap Scan on movie m
   Recheck Cond: (to_tsvector('english'::regconfig, (properties.'plot'::text)::text) @@ '''machin'''::tsquery)
   ->  Bitmap Index Scan on movie_plot_idx
         Index Cond: (to_tsvector('english'::regconfig, (properties.'plot'::text)::text) @@ '''machin'''::tsquery)

The text search configuration used at query time ('english' above) must match the one in the index expression for the index to be used.

On this small demo the planner may still prefer a sequential scan because it is cheaper for so few rows; add SET enable_seqscan = off to observe the index plan. On a real corpus the planner chooses the index automatically.

GIN property indexes over a to_tsvector(...) expression require AgensGraph 2.17 or later.

GraphRAG: combining full-text search with graph traversal

Full-text search can also seed a graph traversal: find the movies whose text best matches the question, then expand along relationships to gather connected context for a language model. This is the lexical counterpart to the GraphRAG example in Vector Search — it shines when the question contains specific terms (names, codes, exact phrases) that keyword search handles better than embeddings.

Connect the films with a few related_to edges — franchise sequels and thematically linked titles:

CREATE ELABEL related_to;

MATCH (a:movie {title: 'The Matrix'}),             (b:movie {title: 'The Matrix Reloaded'})       CREATE (a)-[:related_to]->(b);
MATCH (a:movie {title: 'The Matrix Reloaded'}),    (b:movie {title: 'The Matrix Revolutions'})     CREATE (a)-[:related_to]->(b);
MATCH (a:movie {title: 'The Matrix Revolutions'}), (b:movie {title: 'The Matrix Resurrections'})   CREATE (a)-[:related_to]->(b);
MATCH (a:movie {title: 'The Matrix'}),             (b:movie {title: 'Total Recall'})               CREATE (a)-[:related_to]->(b);
MATCH (a:movie {title: 'The Matrix'}),             (b:movie {title: 'The Terminator'})             CREATE (a)-[:related_to]->(b);
MATCH (a:movie {title: 'Total Recall'}),           (b:movie {title: 'Inception'})                  CREATE (a)-[:related_to]->(b);
MATCH (a:movie {title: 'The Terminator'}),         (b:movie {title: 'Terminator 2: Judgment Day'}) CREATE (a)-[:related_to]->(b);
MATCH (a:movie {title: 'Blade Runner'}),           (b:movie {title: 'Blade Runner 2049'})          CREATE (a)-[:related_to]->(b);
MATCH (a:movie {title: 'Interstellar'}),           (b:movie {title: 'Avatar'})                     CREATE (a)-[:related_to]->(b);
MATCH (a:movie {title: 'Jurassic Park'}),          (b:movie {title: 'The Avengers'})               CREATE (a)-[:related_to]->(b);
MATCH (a:movie {title: 'Minority Report'}),        (b:movie {title: 'Inception'})                  CREATE (a)-[:related_to]->(b);

A single Cypher query ranks the movies by full-text relevance to the question (here the term resistance), keeps the best matches, and expands them to their neighbors:

MATCH (seed:movie)
WHERE to_tsvector('english', seed.plot::text) @@ websearch_to_tsquery('english', 'resistance')
WITH seed, ts_rank(to_tsvector('english', seed.plot::text),
                   websearch_to_tsquery('english', 'resistance')) AS rank
ORDER BY rank DESC, seed.title
LIMIT 2
MATCH (seed)-[:related_to]-(ctx:movie)
RETURN seed.title AS seed, collect(DISTINCT ctx.title) AS related_context;
             seed             |               related_context
------------------------------+----------------------------------------------
 "Terminator 2: Judgment Day" | ["The Terminator"]
 "The Terminator"             | ["Terminator 2: Judgment Day", "The Matrix"]
(2 rows)

The WHERE filter and WITH … ORDER BY rank LIMIT select the movies whose text best matches the query (the seeds — the two Terminator films); the second MATCH follows related_to edges to gather related titles. The seeds plus their neighbors form the context passed to the model — and notice the traversal reaches The Matrix, which the keyword resistance never matched but which is linked to the Terminator films by their shared war-against-machines theme. To combine this lexical retrieval with semantic (vector) retrieval before expanding, see Hybrid Search.

Previous Next

© Copyright 2025, SKAI Worldwide Co., Ltd.

EN한국어