module PgORM::FullTextSearch

Overview

Full-text search support using PostgreSQL's tsvector and tsquery.

This module provides a comprehensive interface to PostgreSQL's powerful full-text search capabilities, including:

Basic Usage

# Simple search across multiple columns
Article.search("crystal programming", :title, :content)

# Weighted search (title more important than content)
Article.search_weighted("crystal", {
  title:   FullTextSearch::Weight::A, # Weight 1.0
  content: FullTextSearch::Weight::B, # Weight 0.4
})

# Ranked search (ordered by relevance)
Article.search_ranked("crystal programming", :title, :content)

Advanced Usage

# Phrase search (exact phrase)
Article.search_phrase("crystal programming language", :content)

# Proximity search (words within 5 positions)
Article.search_proximity("crystal", "programming", 5, :content)

# Prefix search (matches crystal, crystalline, etc.)
Article.search_prefix("cryst", :title, :content)

Production Optimization

For better performance, use pre-computed tsvector columns:

-- Add tsvector column
ALTER TABLE articles ADD COLUMN search_vector tsvector;

-- Create GIN index
CREATE INDEX articles_search_idx ON articles USING GIN(search_vector);

-- Auto-update trigger
CREATE TRIGGER articles_search_update
  BEFORE INSERT OR UPDATE ON articles
  FOR EACH ROW EXECUTE FUNCTION
  tsvector_update_trigger(search_vector, 'pg_catalog.english', title, content);

Then use the optimized search methods:

Article.search_vector("crystal programming", :search_vector)
Article.search_vector_ranked("crystal", :search_vector)

Defined in:

pg-orm/full_text_search.cr

Instance Method Summary

Instance Method Detail

def search(query : String, columns : Array(String), config : String = "english") : Collection(self) #

Performs full-text search using to_tsvector and to_tsquery

Article.search("crystal & programming", :title, :content)
Article.search("crystal | ruby", :title, config: "simple")
Article.search("cryst:*", :title)               # Prefix matching
Article.search("crystal", ["title", "content"]) # Array of strings

[View source]
def search(query : String, *columns : Symbol, config : String = "english") : Collection(self) #

Overload: Accepts Symbol columns


[View source]
def search(query : String, *columns : String, config : String = "english") : Collection(self) #

Overload: Accepts String columns


[View source]
def search_phrase(phrase : String, columns : Array(String), config : String = "english") : Collection(self) #

Performs phrase search (exact phrase matching)

Article.search_phrase("crystal programming language", ["content"])
Article.search_phrase("crystal programming", :title, :content)

[View source]
def search_phrase(phrase : String, *columns : Symbol, config : String = "english") : Collection(self) #

Overload: Accepts Symbol columns


[View source]
def search_phrase(phrase : String, *columns : String, config : String = "english") : Collection(self) #

Overload: Accepts String columns


[View source]
def search_plain(text : String, columns : Array(String), config : String = "english") : Collection(self) #

Performs plain text search (automatically converts to tsquery)

Article.search_plain("crystal programming", :title, :content)

[View source]
def search_plain(text : String, *columns : Symbol, config : String = "english") : Collection(self) #

Overload: Accepts Symbol columns


[View source]
def search_plain(text : String, *columns : String, config : String = "english") : Collection(self) #

Overload: Accepts String columns


[View source]
def search_prefix(prefix : String, columns : Array(String), config : String = "english") : Collection(self) #

Performs prefix search

Article.search_prefix("cryst", :title, :content) # Matches crystal, crystalline, etc.

[View source]
def search_prefix(prefix : String, *columns : Symbol, config : String = "english") : Collection(self) #

Overload: Accepts Symbol columns


[View source]
def search_prefix(prefix : String, *columns : String, config : String = "english") : Collection(self) #

Overload: Accepts String columns


[View source]
def search_proximity(word1 : String, word2 : String, distance : Int32, columns : Array(String), config : String = "english") : Collection(self) #

Performs proximity search (words within N positions)

Article.search_proximity("crystal", "programming", 5, :content) # Within 5 words

[View source]
def search_proximity(word1 : String, word2 : String, distance : Int32, *columns : Symbol, config : String = "english") : Collection(self) #

Overload: Accepts Symbol columns


[View source]
def search_proximity(word1 : String, word2 : String, distance : Int32, *columns : String, config : String = "english") : Collection(self) #

Overload: Accepts String columns


[View source]
def search_ranked(query : String, columns : Array(String), config : String = "english", rank_normalization : Int32 | Nil = nil, rank_function : RankFunction = RankFunction::Rank) : Collection(self) #

Performs full-text search and orders by relevance rank

Article.search_ranked("crystal programming", ["title", "content"])
Article.search_ranked("ruby", ["title", "content"], rank_normalization: 1)
Article.search_ranked("ruby", :title, :content, rank_function: RankFunction::RankCD)

[View source]
def search_ranked(query : String, *columns : Symbol, config : String = "english", rank_normalization : Int32 | Nil = nil, rank_function : RankFunction = RankFunction::Rank) : Collection(self) #

Overload: Accepts Symbol columns


[View source]
def search_ranked(query : String, *columns : String, config : String = "english", rank_normalization : Int32 | Nil = nil, rank_function : RankFunction = RankFunction::Rank) : Collection(self) #

Overload: Accepts String columns


[View source]
def search_ranked_weighted(query : String, weighted_columns : Hash(String, Weight), config : String = "english", rank_normalization : Int32 | Nil = nil, rank_function : RankFunction = RankFunction::Rank) : Collection(self) #

Performs full-text search with weighted columns and ranking

Article.search_ranked_weighted("crystal", {"title" => Weight::A, "content" => Weight::B})
Article.search_ranked_weighted("crystal", {title: Weight::A, content: Weight::B})

[View source]
def search_ranked_weighted(query : String, weighted_columns : Hash(Symbol, Weight), config : String = "english", rank_normalization : Int32 | Nil = nil, rank_function : RankFunction = RankFunction::Rank) : Collection(self) #

Overload: Accepts Symbol keys for weighted columns


[View source]
def search_vector(query : String, vector_column : String, config : String = "english") : Collection(self) #

Searches using a pre-computed tsvector column (recommended for production)

# Setup (run once):
# ALTER TABLE articles ADD COLUMN search_vector tsvector;
# CREATE INDEX articles_search_idx ON articles USING GIN(search_vector);
# CREATE TRIGGER articles_search_update BEFORE INSERT OR UPDATE ON articles
#   FOR EACH ROW EXECUTE FUNCTION
#   tsvector_update_trigger(search_vector, 'pg_catalog.english', title, content);

Article.search_vector("crystal & programming", "search_vector")
Article.search_vector("crystal | ruby", :search_vector, config: "simple")

[View source]
def search_vector(query : String, vector_column : Symbol, config : String = "english") : Collection(self) #

Overload: Accepts Symbol for vector column


[View source]
def search_vector_plain(text : String, vector_column : String, config : String = "english") : Collection(self) #

Searches using a pre-computed tsvector column with plain text query

Article.search_vector_plain("crystal programming", "search_vector")
Article.search_vector_plain("crystal programming", :search_vector)

[View source]
def search_vector_plain(text : String, vector_column : Symbol, config : String = "english") : Collection(self) #

Overload: Accepts Symbol for vector column


[View source]
def search_vector_ranked(query : String, vector_column : String, config : String = "english", rank_normalization : Int32 | Nil = nil, rank_function : RankFunction = RankFunction::Rank) : Collection(self) #

Searches using a pre-computed tsvector column with ranking

Article.search_vector_ranked("crystal", "search_vector")
Article.search_vector_ranked("crystal", :search_vector, rank_normalization: 1)
Article.search_vector_ranked("crystal", :search_vector, rank_function: RankFunction::RankCD)

[View source]
def search_vector_ranked(query : String, vector_column : Symbol, config : String = "english", rank_normalization : Int32 | Nil = nil, rank_function : RankFunction = RankFunction::Rank) : Collection(self) #

Overload: Accepts Symbol for vector column


[View source]
def search_weighted(query : String, weighted_columns : Hash(String, Weight), config : String = "english") : Collection(self) #

Performs full-text search with weighted columns

Article.search_weighted("crystal", {"title" => Weight::A, "content" => Weight::B})
Article.search_weighted("crystal", {title: Weight::A, content: Weight::B})

[View source]
def search_weighted(query : String, weighted_columns : Hash(Symbol, Weight), config : String = "english") : Collection(self) #

Overload: Accepts Symbol keys for weighted columns


[View source]