PostgreSQL FTS
kham-pg เป็น extension ค้นหาข้อความแบบ Full-Text สำหรับ PostgreSQL แต่ละ token ภาษาไทยขยายเป็น lexeme สูงสุด 6 ตัวในตำแหน่งเดียวกัน: คำเอง, รหัส soundex lk82, RTGS, รูป ASCII (สำหรับเลขไทย) และ tag ชนิดคำ (POS) คำ stopword จะถูกกรองออกอัตโนมัติ
Docker Hub (เริ่มใช้งานเร็วที่สุด)
ดึง image พร้อมใช้ — ไม่ต้องติดตั้ง Rust, ไม่ต้อง build เอง รองรับ amd64 + arm64
# PostgreSQL 17 พร้อม kham_pg ติดตั้งไว้แล้ว (latest = PG 17)
docker run --rm -e POSTGRES_PASSWORD=secret \
-p 5432:5432 nickmsft/kham-pg:latest
# ระบุ PostgreSQL version — tags ที่มี:
# v0.8.2-pg14 v0.8.2-pg16 v0.8.2-pg17 v0.8.2-pg18
docker run --rm -e POSTGRES_PASSWORD=secret \
-p 5432:5432 nickmsft/kham-pg:v0.8.2-pg17
# เชื่อมต่อและใช้งานได้เลย
psql -h localhost -U postgres -c "
CREATE EXTENSION kham_pg;
SELECT to_tsvector('kham', 'กินข้าวกับปลา');
" สิ่งที่ต้องมีก่อน
PostgreSQL 14 ขึ้นไป, Rust toolchain (1.85+) และ pg_config ใน PATH
pg_config --version # PostgreSQL 14 หรือใหม่กว่า Build และติดตั้ง
cargo build -p kham-pg --release
make -C kham-pg install Load และใช้งาน
extension มี configuration kham พร้อมใช้ทันที คำ stopword ภาษาไทยถูกกรองออกอัตโนมัติโดยไม่ต้องตั้งค่าเพิ่ม
-- โหลด extension
CREATE EXTENSION kham_pg;
-- ค้นหาข้อความแบบ full-text ด้วย configuration kham ในตัว
SELECT to_tsvector('kham', 'กินข้าวกับปลา') @@ plainto_tsquery('kham', 'ปลา');
-- t (กับ เป็น stopword จึงถูกกรองออก; ปลา ถูก index ไว้) Index เอกสาร
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title TEXT,
body TEXT,
tsv TSVECTOR
);
UPDATE articles
SET tsv = to_tsvector('kham', coalesce(title, '') || ' ' || coalesce(body, ''));
CREATE INDEX articles_tsv_idx ON articles USING GIN(tsv);
CREATE TRIGGER articles_tsv_update
BEFORE INSERT OR UPDATE ON articles
FOR EACH ROW EXECUTE FUNCTION
tsvector_update_trigger(tsv, 'public.kham', title, body); ค้นหาด้วย ts_headline
SELECT id,
ts_headline('kham', body, query,
'StartSel=<mark>, StopSel=</mark>') AS snippet
FROM articles,
to_tsquery('kham', 'ข้าว & ปลา') AS query
WHERE tsv @@ query
ORDER BY ts_rank(tsv, query) DESC; ค้นหาแบบ phonetic และ romanization
token ภาษาไทยทุกตัวจะขยายด้วยรหัส soundex lk82 และ RTGS romanization — คำพ้องเสียงและ query ภาษาอังกฤษจึงเจอเอกสารภาษาไทยได้ทันที
-- query ภาษาอังกฤษ (RTGS) ค้นหาเอกสารภาษาไทย
SELECT to_tsvector('kham', 'กินข้าวกับปลา') @@ plainto_tsquery('kham', 'pla');
-- t (ปลา → 'pla' ถูก index ไว้ในตำแหน่งเดียวกัน)
-- ค้นหาด้วยรหัส lk82 ของ ปลา (4800)
SELECT title FROM articles WHERE tsv @@ to_tsquery('kham', '4800'); การแปลงตัวเลขไทย
ตัวเลขไทย (๑๒๓) ถูก index พร้อมกับรูป ASCII (123) ในตำแหน่งเดียวกัน
-- ค้นหา ๑๒๓ ด้วยตัวเลข ASCII
SELECT to_tsvector('kham', '๑๒๓') @@ plainto_tsquery('kham', '123') AS found;
-- t การกรองด้วย POS lexeme
token ที่รู้ชนิดคำจะมี pos_<tag> เป็น lexeme เพิ่ม ใช้ ::tsquery cast เพื่อให้ underscore ไม่ถูกตัดแยก
-- หาเอกสารที่มีคำกริยา
SELECT title FROM articles WHERE tsv @@ 'pos_verb'::tsquery;
-- ดู POS tags ที่ถูก index สำหรับคำนั้น
SELECT lexeme FROM unnest(to_tsvector('kham', 'ปลา'))
WHERE lexeme LIKE 'pos_%';
-- pos_noun Dictionary soundex variants
มี dictionary variants สองแบบสำหรับการจับเสียงที่ละเอียดกว่า
-- udom83 soundex
CREATE TEXT SEARCH CONFIGURATION kham_udom83 (PARSER = kham);
ALTER TEXT SEARCH CONFIGURATION kham_udom83
ADD MAPPING FOR thai, named WITH kham_fts_dict_udom83;
ALTER TEXT SEARCH CONFIGURATION kham_udom83
ADD MAPPING FOR latin, number, unknown WITH kham_dict;
-- MetaSound
CREATE TEXT SEARCH CONFIGURATION kham_metasound (PARSER = kham);
ALTER TEXT SEARCH CONFIGURATION kham_metasound
ADD MAPPING FOR thai, named WITH kham_fts_dict_metasound;
ALTER TEXT SEARCH CONFIGURATION kham_metasound
ADD MAPPING FOR latin, number, unknown WITH kham_dict; การจัดอันดับและ field boosting
ใช้ ts_rank() หรือ ts_rank_cd() เรียงผลตามความเกี่ยวข้อง และใช้ setweight() เพิ่มน้ำหนักแต่ละ field — น้ำหนัก A (1.0) > B (0.4) > C (0.2) > D (0.1)
-- จัดอันดับพื้นฐาน
SELECT id, ts_rank(tsv, query) AS rank
FROM articles, to_tsquery('kham', 'ข้าว') query
WHERE tsv @@ query
ORDER BY rank DESC;
-- Field boosting: token น้ำหนัก A มีค่า ~5× เทียบกับน้ำหนัก C
UPDATE articles
SET tsv =
setweight(to_tsvector('kham', coalesce(title, '')), 'A') ||
setweight(to_tsvector('kham', coalesce(body, '')), 'C');