feat: pg_trgm full-text search for posts (#2)
* feat(db): add pg_trgm search index on posts * feat(api): use pg_trgm similarity search for posts * fix(api): use ILIKE + word_similarity instead of % operator for search
This commit is contained in:
parent
e74b9f3c39
commit
959d813630
11
migrations/003_search_trgm.sql
Normal file
11
migrations/003_search_trgm.sql
Normal file
@ -0,0 +1,11 @@
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
|
||||
ALTER TABLE posts ADD COLUMN IF NOT EXISTS search_text TEXT
|
||||
GENERATED ALWAYS AS (
|
||||
COALESCE(title, '') || ' ' ||
|
||||
COALESCE(summary, '') || ' ' ||
|
||||
COALESCE(content_md, '')
|
||||
) STORED;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_posts_search_trgm
|
||||
ON posts USING GIN (search_text gin_trgm_ops);
|
||||
@ -801,22 +801,27 @@ pub async fn get_post_stats() -> Result<PostStatsResponse, ServerFnError> {
|
||||
pub async fn search_posts(query: String) -> Result<PostListResponse, ServerFnError> {
|
||||
let client = get_conn().await.map_err(db_conn_error)?;
|
||||
|
||||
let search_pattern = format!("%{}%", query);
|
||||
let q = query.trim();
|
||||
if q.is_empty() {
|
||||
return Ok(PostListResponse { posts: Vec::new() });
|
||||
}
|
||||
|
||||
let rows = client
|
||||
.query(
|
||||
"SELECT
|
||||
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md, p.content_html,
|
||||
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
|
||||
COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags
|
||||
COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags,
|
||||
word_similarity(p.search_text, $1) AS sml
|
||||
FROM posts p
|
||||
LEFT JOIN post_tags pt ON p.id = pt.post_id
|
||||
LEFT JOIN tags t ON pt.tag_id = t.id
|
||||
WHERE p.status = 'published' AND p.deleted_at IS NULL
|
||||
AND (p.title ILIKE $1 OR p.content_md ILIKE $1)
|
||||
GROUP BY p.id
|
||||
ORDER BY p.published_at DESC",
|
||||
&[&search_pattern],
|
||||
AND p.search_text ILIKE '%' || $1 || '%'
|
||||
GROUP BY p.id, p.search_text
|
||||
ORDER BY sml DESC, p.published_at DESC
|
||||
LIMIT 50",
|
||||
&[&q],
|
||||
)
|
||||
.await
|
||||
.map_err(query_error)?;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user