From fad93be0420654c19fa89053121cb8381abdee7f Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <andrew@dunslane.net>
Date: Sat, 22 Aug 2026 17:45:54 -0400
Subject: [PATCH 1/4] Provide a C-ctype variant expected file for test_regex_utf8
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

test_regex_utf8 decides whether to run by looking at the database
encoding alone, but two of its cases, [[:graph:]] and [[:print:]] over
E'xᔀሷ', depend on the ctype as well.  In a database with encoding
UTF8 and locale C they match just the x, because isgraph() and isprint()
are false for anything outside ASCII, and the file fails.  That is the
right answer for such a database; the test was wrong to assume there
could only be one.

Fix by providing a second expected file holding the C-ctype answers.

This applies to REL_15_STABLE and REL_16_STABLE only.
---
 .../test_regex/expected/test_regex_utf8_2.out | 206 ++++++++++++++++++
 1 file changed, 206 insertions(+)
 create mode 100644 src/test/modules/test_regex/expected/test_regex_utf8_2.out

diff --git a/src/test/modules/test_regex/expected/test_regex_utf8_2.out b/src/test/modules/test_regex/expected/test_regex_utf8_2.out
new file mode 100644
index 00000000000..46693c8b74f
--- /dev/null
+++ b/src/test/modules/test_regex/expected/test_regex_utf8_2.out
@@ -0,0 +1,206 @@
+/*
+ * This test must be run in a database with UTF-8 encoding,
+ * because other encodings don't support all the characters used.
+ */
+SELECT getdatabaseencoding() <> 'UTF8'
+       AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+set client_encoding = utf8;
+set standard_conforming_strings = on;
+-- Run the Tcl test cases that require Unicode
+-- expectMatch	9.44 EMP*	{a[\u00fe-\u0507][\u00ff-\u0300]b} \
+-- 	"a\u0102\u02ffb"	"a\u0102\u02ffb"
+select * from test_regex('a[\u00fe-\u0507][\u00ff-\u0300]b', E'a\u0102\u02ffb', 'EMP*');
+               test_regex               
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_UUNPORT}
+ {aĂ˿b}
+(2 rows)
+
+-- expectMatch	13.27 P		"a\\U00001234x"	"a\u1234x"	"a\u1234x"
+select * from test_regex('a\U00001234x', E'a\u1234x', 'P');
+    test_regex     
+-------------------
+ {0,REG_UNONPOSIX}
+ {aሴx}
+(2 rows)
+
+-- expectMatch	13.28 P		{a\U00001234x}	"a\u1234x"	"a\u1234x"
+select * from test_regex('a\U00001234x', E'a\u1234x', 'P');
+    test_regex     
+-------------------
+ {0,REG_UNONPOSIX}
+ {aሴx}
+(2 rows)
+
+-- expectMatch	13.29 P		"a\\U0001234x"	"a\u1234x"	"a\u1234x"
+-- Tcl has relaxed their code to allow 1-8 hex digits, but Postgres hasn't
+select * from test_regex('a\U0001234x', E'a\u1234x', 'P');
+ERROR:  invalid regular expression: invalid escape \ sequence
+-- expectMatch	13.30 P		{a\U0001234x}	"a\u1234x"	"a\u1234x"
+-- Tcl has relaxed their code to allow 1-8 hex digits, but Postgres hasn't
+select * from test_regex('a\U0001234x', E'a\u1234x', 'P');
+ERROR:  invalid regular expression: invalid escape \ sequence
+-- expectMatch	13.31 P		"a\\U000012345x"	"a\u12345x"	"a\u12345x"
+select * from test_regex('a\U000012345x', E'a\u12345x', 'P');
+    test_regex     
+-------------------
+ {0,REG_UNONPOSIX}
+ {aሴ5x}
+(2 rows)
+
+-- expectMatch	13.32 P		{a\U000012345x}	"a\u12345x"	"a\u12345x"
+select * from test_regex('a\U000012345x', E'a\u12345x', 'P');
+    test_regex     
+-------------------
+ {0,REG_UNONPOSIX}
+ {aሴ5x}
+(2 rows)
+
+-- expectMatch	13.33 P		"a\\U1000000x"	"a\ufffd0x"	"a\ufffd0x"
+-- Tcl allows this as a standalone character, but Postgres doesn't
+select * from test_regex('a\U1000000x', E'a\ufffd0x', 'P');
+ERROR:  invalid regular expression: invalid escape \ sequence
+-- expectMatch	13.34 P		{a\U1000000x}	"a\ufffd0x"	"a\ufffd0x"
+-- Tcl allows this as a standalone character, but Postgres doesn't
+select * from test_regex('a\U1000000x', E'a\ufffd0x', 'P');
+ERROR:  invalid regular expression: invalid escape \ sequence
+-- Additional tests, not derived from Tcl
+-- Exercise logic around high character ranges a bit more
+select * from test_regex('a
+  [\u1000-\u1100]*
+  [\u3000-\u3100]*
+  [\u1234-\u25ff]+
+  [\u2000-\u35ff]*
+  [\u2600-\u2f00]*
+  \u1236\u1236x',
+  E'a\u1234\u1236\u1236x', 'xEMP');
+               test_regex               
+----------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_UUNPORT}
+ {aሴሶሶx}
+(2 rows)
+
+select * from test_regex('[[:alnum:]]*[[:upper:]]*[\u1000-\u2000]*\u1237',
+  E'\u1500\u1237', 'ELMP');
+                     test_regex                     
+----------------------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_UUNPORT,REG_ULOCALE}
+ {ᔀሷ}
+(2 rows)
+
+select * from test_regex('[[:alnum:]]*[[:upper:]]*[\u1000-\u2000]*\u1237',
+  E'A\u1239', 'ELMP');
+                     test_regex                     
+----------------------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_UUNPORT,REG_ULOCALE}
+(1 row)
+
+select * from test_regex('[[:alnum:]]*[[:upper:]]*[\u1000-\u2000]*\u1237',
+  E'\u1500\u1237', 'iELMP');
+                     test_regex                     
+----------------------------------------------------
+ {0,REG_UBBS,REG_UNONPOSIX,REG_UUNPORT,REG_ULOCALE}
+ {ᔀሷ}
+(2 rows)
+
+-- systematically test char classes
+select * from test_regex('[[:alnum:]]+',  E'x*\u1500\u1237', 'L');
+   test_regex    
+-----------------
+ {0,REG_ULOCALE}
+ {x}
+(2 rows)
+
+select * from test_regex('[[:alpha:]]+',  E'x*\u1500\u1237', 'L');
+   test_regex    
+-----------------
+ {0,REG_ULOCALE}
+ {x}
+(2 rows)
+
+select * from test_regex('[[:ascii:]]+',  E'x\u1500\u1237', 'L');
+   test_regex    
+-----------------
+ {0,REG_ULOCALE}
+ {x}
+(2 rows)
+
+select * from test_regex('[[:blank:]]+',  E'x \t\u1500\u1237', 'L');
+   test_regex    
+-----------------
+ {0,REG_ULOCALE}
+ {"      "}
+(2 rows)
+
+select * from test_regex('[[:cntrl:]]+',  E'x\u1500\u1237', 'L');
+   test_regex    
+-----------------
+ {0,REG_ULOCALE}
+(1 row)
+
+select * from test_regex('[[:digit:]]+',  E'x9\u1500\u1237', 'L');
+   test_regex    
+-----------------
+ {0,REG_ULOCALE}
+ {9}
+(2 rows)
+
+select * from test_regex('[[:graph:]]+',  E'x\u1500\u1237', 'L');
+   test_regex    
+-----------------
+ {0,REG_ULOCALE}
+ {x}
+(2 rows)
+
+select * from test_regex('[[:lower:]]+',  E'x\u1500\u1237', 'L');
+   test_regex    
+-----------------
+ {0,REG_ULOCALE}
+ {x}
+(2 rows)
+
+select * from test_regex('[[:print:]]+',  E'x\u1500\u1237', 'L');
+   test_regex    
+-----------------
+ {0,REG_ULOCALE}
+ {x}
+(2 rows)
+
+select * from test_regex('[[:punct:]]+',  E'x.\u1500\u1237', 'L');
+   test_regex    
+-----------------
+ {0,REG_ULOCALE}
+ {.}
+(2 rows)
+
+select * from test_regex('[[:space:]]+',  E'x \t\u1500\u1237', 'L');
+   test_regex    
+-----------------
+ {0,REG_ULOCALE}
+ {"      "}
+(2 rows)
+
+select * from test_regex('[[:upper:]]+',  E'xX\u1500\u1237', 'L');
+   test_regex    
+-----------------
+ {0,REG_ULOCALE}
+ {X}
+(2 rows)
+
+select * from test_regex('[[:xdigit:]]+',  E'xa9\u1500\u1237', 'L');
+   test_regex    
+-----------------
+ {0,REG_ULOCALE}
+ {a9}
+(2 rows)
+
+select * from test_regex('[[:word:]]+',  E'x_*\u1500\u1237', 'L');
+   test_regex    
+-----------------
+ {0,REG_ULOCALE}
+ {x_}
+(2 rows)
+
-- 
2.43.0

