-- test/sql/utf8_fuzzy.sql -- Regression test: approximate (k>0) matching over multibyte UTF-8 text. -- -- The k>0 tiling spine is codepoint-based (pg_tre_hash_trigram_cp), so a -- fuzzy query over CJK / accented text produces trigram hashes that match -- how ambuild hashed the indexed text. Before the fix the k>0 spine slid -- over raw UTF-8 bytes and dropped codepoints > 0xFF, silently missing -- matching rows (false negatives). The correctness gate below is the -- index vs. seq-scan differential for multibyte patterns at k>=1. CREATE EXTENSION IF NOT EXISTS pg_tre; psql:test/sql/utf8_fuzzy.sql:11: NOTICE: extension "pg_tre" already exists, skipping CREATE EXTENSION DROP TABLE IF EXISTS u8_t CASCADE; psql:test/sql/utf8_fuzzy.sql:13: NOTICE: table "u8_t" does not exist, skipping DROP TABLE CREATE TABLE u8_t (id serial, body text); CREATE TABLE INSERT INTO u8_t (body) VALUES ('café society'), -- é U+00E9 (2 bytes) ('cafe society'), -- ascii near-variant (1 edit from café) ('naïve approach'), -- ï U+00EF ('日本語のテスト'), -- CJK ('日本語のテキスト'), -- CJK near-variant ('résumé draft'), -- multiple accents ('unrelated ascii text'); INSERT 0 7 CREATE INDEX u8_idx ON u8_t USING tre (body); psql:test/sql/utf8_fuzzy.sql:25: NOTICE: pg_tre: collected 71 trigram entries from 7 heap tuples psql:test/sql/utf8_fuzzy.sql:25: NOTICE: pg_tre: built 61 posting trees psql:test/sql/utf8_fuzzy.sql:25: NOTICE: pg_tre: build complete, indexed 7 heap tuples into 61 trigrams CREATE INDEX -- Exact multibyte (k=0). SET enable_seqscan = off; SET SELECT id FROM u8_t WHERE body %~~ tre_pattern('café', 0) ORDER BY id; id ---- 1 (1 row) -- Fuzzy multibyte (k=1): 'café' within 1 edit should still find 'café'; -- the whole point is the codepoint-aware spine keeps this correct. SELECT id FROM u8_t WHERE body %~~ tre_pattern('café', 1) ORDER BY id; id ---- 1 2 (2 rows) -- CJK fuzzy (k=1). SELECT id FROM u8_t WHERE body %~~ tre_pattern('日本語のテスト', 1) ORDER BY id; id ---- 4 5 (2 rows) RESET enable_seqscan; RESET -- Differential: same queries via seq scan must return identical rows. SET enable_indexscan = off; SET SET enable_bitmapscan = off; SET SELECT id FROM u8_t WHERE body %~~ tre_pattern('café', 0) ORDER BY id; id ---- 1 (1 row) SELECT id FROM u8_t WHERE body %~~ tre_pattern('café', 1) ORDER BY id; id ---- 1 2 (2 rows) SELECT id FROM u8_t WHERE body %~~ tre_pattern('日本語のテスト', 1) ORDER BY id; id ---- 4 5 (2 rows) RESET enable_indexscan; RESET RESET enable_bitmapscan; RESET -- Edits are counted in characters, whatever the database's LC_CTYPE. -- Under a C/POSIX ctype TRE used to count bytes: 'café' was 2 edits from -- 'cafe' and キ (3 bytes) was 3 edits from ス, so the k=1 queries above -- returned different rows on a C and a C.UTF-8 database. SELECT tre_amatch_cost('cafe', 'café', 5) AS cost_e_acute, tre_amatch_cost('日本語のテキスト', '日本語のテスト', 5) AS cost_cjk, tre_amatch_cost('naïve', 'naive', 5) AS cost_i_diaeresis; cost_e_acute | cost_cjk | cost_i_diaeresis --------------+----------+------------------ 1 | 1 | 1 (1 row) -- Match offsets stay byte offsets (callers slice the text by bytes). SELECT match_start, match_end FROM tre_amatch_detail('xx café yy', 'café', 0); match_start | match_end -------------+----------- 3 | 8 (1 row) DROP TABLE u8_t CASCADE; DROP TABLE