use super::*;
#[derive(Debug, Clone, Default)]
pub struct FullTextConfig {
pub language: Option<String>,
pub mode: SearchMode,
pub min_word_length: Option<u32>,
pub max_word_length: Option<u32>,
pub stop_words: Vec<String>,
pub weights: Option<SearchWeights>,
}
impl FullTextConfig {
pub fn new() -> Self {
Self::default()
}
pub fn language(mut self, lang: impl Into<String>) -> Self {
self.language = Some(lang.into());
self
}
pub fn mode(mut self, mode: SearchMode) -> Self {
self.mode = mode;
self
}
pub fn min_word_length(mut self, len: u32) -> Self {
self.min_word_length = Some(len);
self
}
pub fn max_word_length(mut self, len: u32) -> Self {
self.max_word_length = Some(len);
self
}
pub fn stop_words(mut self, words: Vec<String>) -> Self {
self.stop_words = words;
self
}
pub fn weights(mut self, weights: SearchWeights) -> Self {
self.weights = Some(weights);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SearchMode {
#[default]
Natural,
Boolean,
Phrase,
Prefix,
Fuzzy,
Proximity(u32),
}
impl fmt::Display for SearchMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SearchMode::Natural => write!(f, "natural"),
SearchMode::Boolean => write!(f, "boolean"),
SearchMode::Phrase => write!(f, "phrase"),
SearchMode::Prefix => write!(f, "prefix"),
SearchMode::Fuzzy => write!(f, "fuzzy"),
SearchMode::Proximity(d) => write!(f, "proximity({})", d),
}
}
}
#[derive(Debug, Clone)]
pub struct SearchWeights {
pub a: f32,
pub b: f32,
pub c: f32,
pub d: f32,
}
impl Default for SearchWeights {
fn default() -> Self {
Self {
a: 1.0,
b: 0.4,
c: 0.2,
d: 0.1,
}
}
}
impl SearchWeights {
pub fn new(a: f32, b: f32, c: f32, d: f32) -> Self {
Self { a, b, c, d }
}
pub fn to_pg_array(&self) -> String {
format!("'{{{},{},{},{}}}'", self.d, self.c, self.b, self.a)
}
}
#[derive(Debug, Clone)]
pub struct SearchResult<T> {
pub record: T,
pub rank: f64,
pub highlights: Vec<HighlightedField>,
}
impl<T> SearchResult<T> {
pub fn new(record: T, rank: f64) -> Self {
Self {
record,
rank,
highlights: Vec::new(),
}
}
pub fn with_highlights(mut self, highlights: Vec<HighlightedField>) -> Self {
self.highlights = highlights;
self
}
}
#[derive(Debug, Clone)]
pub struct HighlightedField {
pub field: String,
pub highlighted: String,
pub original: String,
pub match_count: usize,
}
impl HighlightedField {
pub fn new(
field: impl Into<String>,
highlighted: impl Into<String>,
original: impl Into<String>,
) -> Self {
let highlighted = highlighted.into();
let original = original.into();
let match_count = highlighted.matches("<mark>").count();
Self {
field: field.into(),
highlighted,
original,
match_count,
}
}
}
pub trait FullTextSearch: Model + Sized {
fn search(columns: &[&str], query: &str) -> FullTextSearchBuilder<Self> {
FullTextSearchBuilder::new(columns, query)
}
fn search_with_config(
columns: &[&str],
query: &str,
config: FullTextConfig,
) -> FullTextSearchBuilder<Self> {
FullTextSearchBuilder::new(columns, query).config(config)
}
fn search_ranked(columns: &[&str], query: &str) -> FullTextSearchBuilder<Self> {
FullTextSearchBuilder::new(columns, query).with_ranking()
}
fn search_highlighted(
columns: &[&str],
query: &str,
start_tag: &str,
end_tag: &str,
) -> FullTextSearchBuilder<Self> {
FullTextSearchBuilder::new(columns, query).with_highlights(start_tag, end_tag)
}
}
impl<T: Model> FullTextSearch for T {}