| From: | PG Bug reporting form <noreply(at)postgresql(dot)org> |
|---|---|
| To: | pgsql-bugs(at)lists(dot)postgresql(dot)org |
| Cc: | 303677365(at)qq(dot)com |
| Subject: | BUG #19832: convert() between BIG5 and EUC_TW returns different characters than PostgreSQL's own UTF-8 mappings |
| Date: | 2026-11-05 09:32:54 |
| Message-ID: | 19832-7f2119af7a736b33@postgresql.org |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-bugs |
The following bug has been logged on the website:
Bug reference: 19832
Logged by: chunling qin
Email address: 303677365(at)qq(dot)com
PostgreSQL version: 18.6
Operating system: Linux x86_64 (official Docker image postgres:lates
Description:
**Description:**
This is the same family of problem as bug #19831 (single-byte Cyrillic
conversions), but in the multi-byte BIG5 <-> EUC_TW pair: `convert()` takes
a *direct* conversion path (the BIG5<->CNS 11643 tables registered in
`pg_conversion`), while `convert_to()`/`convert_from()` go through UTF-8
using the Unicode-derived generated maps. For 22 byte sequences the two
paths decode to *different characters*.
One caveat up front: I am not aware of an independent third-party EUC_TW
oracle to adjudicate which character is "correct" for a given BIG5 code. The
evidence below is therefore framed as an internal inconsistency: two code
paths inside PostgreSQL itself, both claiming to convert between the same
two encodings, return different characters, and the swapped-pair structure
(below) points to entries in the direct tables being shifted, rather than a
legitimate mapping difference.
**Reproduction (self-contained, works on the official image):**
```sh
docker exec -i <postgres:latest container> psql -U postgres -X -P pager=off
<<'SQL'
-- BIG5 0xa1 0x55 is U+FF5C (FULLWIDTH VERTICAL LINE) per PG's own
big5_to_utf8.map
SELECT convert_from(decode('a155','hex'), 'BIG5')
AS src_char,
convert_from(convert(decode('a155','hex'),'BIG5','EUC_TW'),'EUC_TW')
AS direct_char,
convert_from(convert_to(convert_from(decode('a155','hex'),'BIG5'),
'EUC_TW'),'EUC_TW')
AS twostep_char;
-- direct_char = ︱ (U+FE31 PRESENTATION FORM FOR VERTICAL EM DASH)
-- twostep_char = | (U+FF5C) = src_char
SQL
```
**Forward direction (BIG5 -> EUC_TW), 12 cases; in every case `direct_char`
is a different character from `src_char`, while `twostep_char` (PG's own
UTF-8 maps) equals `src_char`:**
| BIG5 | src_char | direct result | twostep result |
|---|---|---|---|
| a155 | | U+FF5C | a1b6 = ︱ U+FE31 | a2de = | |
| a156 | – U+2013 | a1b7 = — U+2014 | a1b9 = – |
| a157 | ︱ U+FE31 | a1b8 = ︲ | a1b6 = ︱ |
| a158 | — U+2014 | a1b9 = – U+2013 | a1a7 = — |
| a1ab | ‵ U+2035 | a1ea = ′ U+2032 | a1eb = ‵ |
| a1ac | ′ U+2032 | a1eb = ‵ U+2035 | a1ea = ′ |
| a1e1 | ﹥ | a2c2 = ﹦ | a2c3 = ﹥ |
| a1e2 | ﹦ | a2c3 = ﹥ | a2c2 = ﹦ |
| a241 | / U+FF0F | a2e1 = ∕ U+2215 | a2df = / |
| a242 | \ U+FF3C | a2e2 = ﹨ | a2e0 = \ |
| d6cc | 筇 | 8ea2b7ef = 笻 | 8ea2b7ef... = 筇 |
| dadf | 笻 | 8ea2bee3 = 筇 | 8ea2bee3... = 笻 |
(For the last two, the CNS-plane encodings are shown as EUC_TW byte
sequences; direct and two-step swap the two CJK characters.)
The structure is striking: the direct result of input X is the character
that PG's own maps assign to input Y, and vice versa — a155<->a157,
a156<->a158, a1ab<->a1ac, a1e1<->a1e2, d6cc<->dadf. The direct BIG5->CNS
entries look **shifted/swapped by one position** relative to the
Unicode-derived maps, which is a typical transcription bug pattern rather
than a mapping-convention difference.
**Reverse direction (EUC_TW -> BIG5):** 8 further cases show the same
disagreement and the same swapped-pair structure (a1b6/a1b7, a1ea/a1eb,
a2c2/a2c3, a2de/a2df ...). Two additional reverse cases (a2df/a2e0) involve
U+FFFD replacement-character entries in `big5_to_utf8.map` (a known separate
mapping difference) and are excluded from this report.
**Root cause:**
`src/backend/utils/mb/conversion_procs/euc_tw_and_big5/big5.c` —
`BIG5toCNS()` / `CNStoBIG5()` use legacy hand-maintained BIG5<->CNS 11643
tables, whose entries disagree for the byte pairs above with the
Unicode-derived generated maps
`src/backend/utils/mb/Unicode/big5_to_utf8.map` (from `UCS_to_BIG5.pl`) and
`euc_tw_to_utf8.map` / `utf8_to_euc_tw.map` (from `UCS_to_EUC_TW.pl`).
**Expected behavior:** as with #19831, the direct conversion and the
composition of PG's own Unicode-derived maps should agree (or the direct
tables should be dropped in favor of routing through UTF-8). Given the
swapped-pair structure, the most likely fix is correcting the shifted
entries in the BIG5<->CNS tables; if instead the legacy tables are
considered authoritative for these pairs, then PG's two code paths will keep
giving different answers for the same conversion, which is at minimum a
consistency defect.
| From | Date | Subject | |
|---|---|---|---|
| Next Message | PG Bug reporting form | 2026-11-05 09:37:25 | BUG #19833: ALTER TABLE that rebuilds an extended statistics object silently drops its SET STATISTICS target (st |
| Previous Message | Dilip Kumar | 2026-11-05 09:28:35 | Re: BUG #19817: CREATE UNIQUE INDEX on a partitioned table can silently attach an invalid, not-ready child index, le |