Branch data Line data Source code
1 : : /*-----------------------------------------------------------------------
2 : : *
3 : : * PostgreSQL locale utilities for builtin provider
4 : : *
5 : : * Portions Copyright (c) 2002-2026, PostgreSQL Global Development Group
6 : : *
7 : : * src/backend/utils/adt/pg_locale_builtin.c
8 : : *
9 : : *-----------------------------------------------------------------------
10 : : */
11 : :
12 : : #include "postgres.h"
13 : :
14 : : #include "catalog/pg_database.h"
15 : : #include "catalog/pg_collation.h"
16 : : #include "common/unicode_case.h"
17 : : #include "common/unicode_category.h"
18 : : #include "common/unicode_limits.h"
19 : : #include "miscadmin.h"
20 : : #include "utils/builtins.h"
21 : : #include "utils/memutils.h"
22 : : #include "utils/pg_locale.h"
23 : : #include "utils/syscache.h"
24 : :
25 : : /*
26 : : * The largest text value must fit in MaxAllocSize, but then may grow during
27 : : * case mapping. While the resulting string will not be representable as a new
28 : : * text value, we must at least be sure not to overflow a size_t while
29 : : * processing it.
30 : : */
31 : : StaticAssertDecl(SIZE_MAX / UTF8_MAX_CASEMAP_EXPANSION > MaxAllocSize,
32 : : "case mapping may overflow size_t");
33 : :
34 : : extern pg_locale_t create_pg_locale_builtin(Oid collid,
35 : : MemoryContext context);
36 : : extern char *get_collation_actual_version_builtin(const char *collcollate);
37 : :
38 : : struct WordBoundaryState
39 : : {
40 : : const char *str;
41 : : size_t len;
42 : : size_t offset;
43 : : bool posix;
44 : : bool init;
45 : : bool prev_alnum;
46 : : };
47 : :
48 : : /*
49 : : * In UTF-8, pg_wchar is guaranteed to be the code point value.
50 : : */
51 : : static inline char32_t
52 : 129459 : to_char32(pg_wchar wc)
53 : : {
54 : : Assert(GetDatabaseEncoding() == PG_UTF8);
55 : 129459 : return (char32_t) wc;
56 : : }
57 : :
58 : : static inline pg_wchar
59 : 650 : to_pg_wchar(char32_t c32)
60 : : {
61 : : Assert(GetDatabaseEncoding() == PG_UTF8);
62 : 650 : return (pg_wchar) c32;
63 : : }
64 : :
65 : : /*
66 : : * Simple word boundary iterator that draws boundaries each time the result of
67 : : * pg_u_isalnum() changes.
68 : : */
69 : : static size_t
70 : 564 : initcap_wbnext(void *state)
71 : : {
72 : 564 : struct WordBoundaryState *wbstate = (struct WordBoundaryState *) state;
73 : :
74 [ + + ]: 1165 : while (wbstate->offset < wbstate->len)
75 : : {
76 : 1032 : int ulen = pg_utf_mblen((const unsigned char *) wbstate->str +
77 : 1032 : wbstate->offset);
78 : : char32_t u;
79 : : bool curr_alnum;
80 : 1032 : size_t prev_offset = wbstate->offset;
81 : :
82 : : /* invalid UTF8 */
83 [ - + ]: 1032 : if (wbstate->offset + ulen > wbstate->len)
84 : : {
85 : 0 : wbstate->init = true;
86 : 0 : wbstate->offset = wbstate->len;
87 : 0 : return prev_offset;
88 : : }
89 : :
90 : 1032 : u = utf8_to_unicode((const unsigned char *) wbstate->str +
91 : 1032 : wbstate->offset);
92 : 1032 : curr_alnum = pg_u_isalnum(u, wbstate->posix);
93 : :
94 [ + + + + ]: 1032 : if (!wbstate->init || curr_alnum != wbstate->prev_alnum)
95 : : {
96 : 431 : wbstate->init = true;
97 : 431 : wbstate->offset += ulen;
98 : 431 : wbstate->prev_alnum = curr_alnum;
99 : 431 : return prev_offset;
100 : : }
101 : :
102 : 601 : wbstate->offset += ulen;
103 : : }
104 : :
105 : 133 : return wbstate->len;
106 : : }
107 : :
108 : : static size_t
109 : 6321 : strlower_builtin(char *dest, size_t destsize, const char *src, size_t srclen,
110 : : pg_locale_t locale)
111 : : {
112 : : size_t consumed;
113 : : size_t result;
114 : :
115 : 6321 : result = unicode_strlower(dest, destsize, src, srclen, &consumed,
116 : 6321 : locale->builtin.casemap_full);
117 [ - + ]: 6321 : if (consumed < srclen)
118 : 0 : report_invalid_encoding(GetDatabaseEncoding(), src + consumed,
119 : 0 : srclen - consumed);
120 : :
121 : 6321 : return result;
122 : : }
123 : :
124 : : static size_t
125 : 133 : strtitle_builtin(char *dest, size_t destsize, const char *src, size_t srclen,
126 : : pg_locale_t locale)
127 : : {
128 : 133 : struct WordBoundaryState wbstate = {
129 : : .str = src,
130 : : .len = srclen,
131 : : .offset = 0,
132 : 133 : .posix = !locale->builtin.casemap_full,
133 : : .init = false,
134 : : .prev_alnum = false,
135 : : };
136 : : size_t consumed;
137 : : size_t result;
138 : :
139 : 133 : result = unicode_strtitle(dest, destsize, src, srclen, &consumed,
140 : 133 : locale->builtin.casemap_full,
141 : : initcap_wbnext, &wbstate);
142 : :
143 [ - + ]: 133 : if (consumed < srclen)
144 : 0 : report_invalid_encoding(GetDatabaseEncoding(), src + consumed,
145 : 0 : srclen - consumed);
146 : :
147 : 133 : return result;
148 : : }
149 : :
150 : : static size_t
151 : 158561 : strupper_builtin(char *dest, size_t destsize, const char *src, size_t srclen,
152 : : pg_locale_t locale)
153 : : {
154 : : size_t consumed;
155 : : size_t result;
156 : :
157 : 158561 : result = unicode_strupper(dest, destsize, src, srclen, &consumed,
158 : 158561 : locale->builtin.casemap_full);
159 [ - + ]: 158561 : if (consumed < srclen)
160 : 0 : report_invalid_encoding(GetDatabaseEncoding(), src + consumed,
161 : 0 : srclen - consumed);
162 : :
163 : 158561 : return result;
164 : : }
165 : :
166 : : static size_t
167 : 10 : strfold_builtin(char *dest, size_t destsize, const char *src, size_t srclen,
168 : : pg_locale_t locale)
169 : : {
170 : : size_t consumed;
171 : : size_t result;
172 : :
173 : 10 : result = unicode_strfold(dest, destsize, src, srclen, &consumed,
174 : 10 : locale->builtin.casemap_full);
175 [ - + ]: 10 : if (consumed < srclen)
176 : 0 : report_invalid_encoding(GetDatabaseEncoding(), src + consumed,
177 : 0 : srclen - consumed);
178 : :
179 : 10 : return result;
180 : : }
181 : :
182 : : static bool
183 : 43117 : wc_isdigit_builtin(pg_wchar wc, pg_locale_t locale)
184 : : {
185 : 43117 : return pg_u_isdigit(to_char32(wc), !locale->builtin.casemap_full);
186 : : }
187 : :
188 : : static bool
189 : 19901 : wc_isalpha_builtin(pg_wchar wc, pg_locale_t locale)
190 : : {
191 : 19901 : return pg_u_isalpha(to_char32(wc));
192 : : }
193 : :
194 : : static bool
195 : 24708 : wc_isalnum_builtin(pg_wchar wc, pg_locale_t locale)
196 : : {
197 : 24708 : return pg_u_isalnum(to_char32(wc), !locale->builtin.casemap_full);
198 : : }
199 : :
200 : : static bool
201 : 16384 : wc_isupper_builtin(pg_wchar wc, pg_locale_t locale)
202 : : {
203 : 16384 : return pg_u_isupper(to_char32(wc));
204 : : }
205 : :
206 : : static bool
207 : 0 : wc_islower_builtin(pg_wchar wc, pg_locale_t locale)
208 : : {
209 : 0 : return pg_u_islower(to_char32(wc));
210 : : }
211 : :
212 : : static bool
213 : 0 : wc_isgraph_builtin(pg_wchar wc, pg_locale_t locale)
214 : : {
215 : 0 : return pg_u_isgraph(to_char32(wc));
216 : : }
217 : :
218 : : static bool
219 : 0 : wc_isprint_builtin(pg_wchar wc, pg_locale_t locale)
220 : : {
221 : 0 : return pg_u_isprint(to_char32(wc));
222 : : }
223 : :
224 : : static bool
225 : 16384 : wc_ispunct_builtin(pg_wchar wc, pg_locale_t locale)
226 : : {
227 : 16384 : return pg_u_ispunct(to_char32(wc), !locale->builtin.casemap_full);
228 : : }
229 : :
230 : : static bool
231 : 8312 : wc_isspace_builtin(pg_wchar wc, pg_locale_t locale)
232 : : {
233 : 8312 : return pg_u_isspace(to_char32(wc));
234 : : }
235 : :
236 : : static bool
237 : 3 : wc_isxdigit_builtin(pg_wchar wc, pg_locale_t locale)
238 : : {
239 : 3 : return pg_u_isxdigit(to_char32(wc), !locale->builtin.casemap_full);
240 : : }
241 : :
242 : : static bool
243 : 0 : wc_iscased_builtin(pg_wchar wc, pg_locale_t locale)
244 : : {
245 : 0 : return pg_u_prop_cased(to_char32(wc));
246 : : }
247 : :
248 : : static pg_wchar
249 : 325 : wc_toupper_builtin(pg_wchar wc, pg_locale_t locale)
250 : : {
251 : 325 : return to_pg_wchar(unicode_uppercase_simple(to_char32(wc)));
252 : : }
253 : :
254 : : static pg_wchar
255 : 325 : wc_tolower_builtin(pg_wchar wc, pg_locale_t locale)
256 : : {
257 : 325 : return to_pg_wchar(unicode_lowercase_simple(to_char32(wc)));
258 : : }
259 : :
260 : : static const struct ctype_methods ctype_methods_builtin = {
261 : : .strlower = strlower_builtin,
262 : : .strtitle = strtitle_builtin,
263 : : .strupper = strupper_builtin,
264 : : .strfold = strfold_builtin,
265 : : /* uses plain ASCII semantics for historical reasons */
266 : : .downcase_ident = NULL,
267 : : .wc_isdigit = wc_isdigit_builtin,
268 : : .wc_isalpha = wc_isalpha_builtin,
269 : : .wc_isalnum = wc_isalnum_builtin,
270 : : .wc_isupper = wc_isupper_builtin,
271 : : .wc_islower = wc_islower_builtin,
272 : : .wc_isgraph = wc_isgraph_builtin,
273 : : .wc_isprint = wc_isprint_builtin,
274 : : .wc_ispunct = wc_ispunct_builtin,
275 : : .wc_isspace = wc_isspace_builtin,
276 : : .wc_isxdigit = wc_isxdigit_builtin,
277 : : .wc_iscased = wc_iscased_builtin,
278 : : .wc_tolower = wc_tolower_builtin,
279 : : .wc_toupper = wc_toupper_builtin,
280 : : };
281 : :
282 : : pg_locale_t
283 : 967 : create_pg_locale_builtin(Oid collid, MemoryContext context)
284 : : {
285 : : const char *locstr;
286 : : pg_locale_t result;
287 : :
288 [ + + ]: 967 : if (collid == DEFAULT_COLLATION_OID)
289 : : {
290 : : HeapTuple tp;
291 : : Datum datum;
292 : :
293 : 929 : tp = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
294 [ - + ]: 929 : if (!HeapTupleIsValid(tp))
295 [ # # ]: 0 : elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
296 : 929 : datum = SysCacheGetAttrNotNull(DATABASEOID, tp,
297 : : Anum_pg_database_datlocale);
298 : 929 : locstr = TextDatumGetCString(datum);
299 : 929 : ReleaseSysCache(tp);
300 : : }
301 : : else
302 : : {
303 : : HeapTuple tp;
304 : : Datum datum;
305 : :
306 : 38 : tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
307 [ - + ]: 38 : if (!HeapTupleIsValid(tp))
308 [ # # ]: 0 : elog(ERROR, "cache lookup failed for collation %u", collid);
309 : 38 : datum = SysCacheGetAttrNotNull(COLLOID, tp,
310 : : Anum_pg_collation_colllocale);
311 : 38 : locstr = TextDatumGetCString(datum);
312 : 38 : ReleaseSysCache(tp);
313 : : }
314 : :
315 : 967 : builtin_validate_locale(GetDatabaseEncoding(), locstr);
316 : :
317 : 967 : result = MemoryContextAllocZero(context, sizeof(struct pg_locale_struct));
318 : :
319 : 967 : result->builtin.locale = MemoryContextStrdup(context, locstr);
320 : 967 : result->builtin.casemap_full = (strcmp(locstr, "PG_UNICODE_FAST") == 0);
321 : 967 : result->deterministic = true;
322 : 967 : result->collate_is_c = true;
323 : 967 : result->ctype_is_c = (strcmp(locstr, "C") == 0);
324 [ + + ]: 967 : if (!result->ctype_is_c)
325 : 944 : result->ctype = &ctype_methods_builtin;
326 : :
327 : 967 : return result;
328 : : }
329 : :
330 : : char *
331 : 1010 : get_collation_actual_version_builtin(const char *collcollate)
332 : : {
333 : : /*
334 : : * The only two supported locales (C and C.UTF-8) are both based on memcmp
335 : : * and are not expected to change, but track the version anyway.
336 : : *
337 : : * Note that the character semantics may change for some locales, but the
338 : : * collation version only tracks changes to sort order.
339 : : */
340 [ + + ]: 1010 : if (strcmp(collcollate, "C") == 0)
341 : 44 : return "1";
342 [ + + ]: 966 : else if (strcmp(collcollate, "C.UTF-8") == 0)
343 : 953 : return "1";
344 [ + - ]: 13 : else if (strcmp(collcollate, "PG_UNICODE_FAST") == 0)
345 : 13 : return "1";
346 : : else
347 [ # # ]: 0 : ereport(ERROR,
348 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
349 : : errmsg("invalid locale name \"%s\" for builtin provider",
350 : : collcollate)));
351 : :
352 : : return NULL; /* keep compiler quiet */
353 : : }
|