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 "miscadmin.h"
19 : : #include "utils/builtins.h"
20 : : #include "utils/pg_locale.h"
21 : : #include "utils/syscache.h"
22 : :
23 : : extern pg_locale_t create_pg_locale_builtin(Oid collid,
24 : : MemoryContext context);
25 : : extern char *get_collation_actual_version_builtin(const char *collcollate);
26 : :
27 : : struct WordBoundaryState
28 : : {
29 : : const char *str;
30 : : size_t len;
31 : : size_t offset;
32 : : bool posix;
33 : : bool init;
34 : : bool prev_alnum;
35 : : };
36 : :
37 : : /*
38 : : * In UTF-8, pg_wchar is guaranteed to be the code point value.
39 : : */
40 : : static inline char32_t
41 : 16490 : to_char32(pg_wchar wc)
42 : : {
43 [ + - ]: 16490 : Assert(GetDatabaseEncoding() == PG_UTF8);
44 : 16490 : return (char32_t) wc;
45 : : }
46 : :
47 : : static inline pg_wchar
48 : 104 : to_pg_wchar(char32_t c32)
49 : : {
50 [ + - ]: 104 : Assert(GetDatabaseEncoding() == PG_UTF8);
51 : 104 : return (pg_wchar) c32;
52 : : }
53 : :
54 : : /*
55 : : * Simple word boundary iterator that draws boundaries each time the result of
56 : : * pg_u_isalnum() changes.
57 : : */
58 : : static size_t
59 : 136 : initcap_wbnext(void *state)
60 : : {
61 : 136 : struct WordBoundaryState *wbstate = (struct WordBoundaryState *) state;
62 : :
63 [ + + + + ]: 528 : while (wbstate->offset < wbstate->len &&
64 : 248 : wbstate->str[wbstate->offset] != '\0')
65 : : {
66 : 496 : char32_t u = utf8_to_unicode((unsigned char *) wbstate->str +
67 : 248 : wbstate->offset);
68 : 248 : bool curr_alnum = pg_u_isalnum(u, wbstate->posix);
69 : :
70 [ + + + + ]: 248 : if (!wbstate->init || curr_alnum != wbstate->prev_alnum)
71 : : {
72 : 104 : size_t prev_offset = wbstate->offset;
73 : :
74 : 104 : wbstate->init = true;
75 : 104 : wbstate->offset += unicode_utf8len(u);
76 : 104 : wbstate->prev_alnum = curr_alnum;
77 : 104 : return prev_offset;
78 : 104 : }
79 : :
80 : 144 : wbstate->offset += unicode_utf8len(u);
81 [ + + ]: 248 : }
82 : :
83 : 32 : return wbstate->len;
84 : 136 : }
85 : :
86 : : static size_t
87 : 48 : strlower_builtin(char *dest, size_t destsize, const char *src, ssize_t srclen,
88 : : pg_locale_t locale)
89 : : {
90 : 96 : return unicode_strlower(dest, destsize, src, srclen,
91 : 48 : locale->builtin.casemap_full);
92 : : }
93 : :
94 : : static size_t
95 : 32 : strtitle_builtin(char *dest, size_t destsize, const char *src, ssize_t srclen,
96 : : pg_locale_t locale)
97 : : {
98 : 128 : struct WordBoundaryState wbstate = {
99 : 32 : .str = src,
100 : 32 : .len = srclen,
101 : : .offset = 0,
102 : 32 : .posix = !locale->builtin.casemap_full,
103 : : .init = false,
104 : : .prev_alnum = false,
105 : : };
106 : :
107 : 96 : return unicode_strtitle(dest, destsize, src, srclen,
108 : 32 : locale->builtin.casemap_full,
109 : : initcap_wbnext, &wbstate);
110 : 32 : }
111 : :
112 : : static size_t
113 : 28 : strupper_builtin(char *dest, size_t destsize, const char *src, ssize_t srclen,
114 : : pg_locale_t locale)
115 : : {
116 : 56 : return unicode_strupper(dest, destsize, src, srclen,
117 : 28 : locale->builtin.casemap_full);
118 : : }
119 : :
120 : : static size_t
121 : 2 : strfold_builtin(char *dest, size_t destsize, const char *src, ssize_t srclen,
122 : : pg_locale_t locale)
123 : : {
124 : 4 : return unicode_strfold(dest, destsize, src, srclen,
125 : 2 : locale->builtin.casemap_full);
126 : : }
127 : :
128 : : static bool
129 : 4098 : wc_isdigit_builtin(pg_wchar wc, pg_locale_t locale)
130 : : {
131 : 4098 : return pg_u_isdigit(to_char32(wc), !locale->builtin.casemap_full);
132 : : }
133 : :
134 : : static bool
135 : 0 : wc_isalpha_builtin(pg_wchar wc, pg_locale_t locale)
136 : : {
137 : 0 : return pg_u_isalpha(to_char32(wc));
138 : : }
139 : :
140 : : static bool
141 : 4096 : wc_isalnum_builtin(pg_wchar wc, pg_locale_t locale)
142 : : {
143 : 4096 : return pg_u_isalnum(to_char32(wc), !locale->builtin.casemap_full);
144 : : }
145 : :
146 : : static bool
147 : 4096 : wc_isupper_builtin(pg_wchar wc, pg_locale_t locale)
148 : : {
149 : 4096 : return pg_u_isupper(to_char32(wc));
150 : : }
151 : :
152 : : static bool
153 : 0 : wc_islower_builtin(pg_wchar wc, pg_locale_t locale)
154 : : {
155 : 0 : return pg_u_islower(to_char32(wc));
156 : : }
157 : :
158 : : static bool
159 : 0 : wc_isgraph_builtin(pg_wchar wc, pg_locale_t locale)
160 : : {
161 : 0 : return pg_u_isgraph(to_char32(wc));
162 : : }
163 : :
164 : : static bool
165 : 0 : wc_isprint_builtin(pg_wchar wc, pg_locale_t locale)
166 : : {
167 : 0 : return pg_u_isprint(to_char32(wc));
168 : : }
169 : :
170 : : static bool
171 : 4096 : wc_ispunct_builtin(pg_wchar wc, pg_locale_t locale)
172 : : {
173 : 4096 : return pg_u_ispunct(to_char32(wc), !locale->builtin.casemap_full);
174 : : }
175 : :
176 : : static bool
177 : 0 : wc_isspace_builtin(pg_wchar wc, pg_locale_t locale)
178 : : {
179 : 0 : return pg_u_isspace(to_char32(wc));
180 : : }
181 : :
182 : : static bool
183 : 0 : wc_isxdigit_builtin(pg_wchar wc, pg_locale_t locale)
184 : : {
185 : 0 : return pg_u_isxdigit(to_char32(wc), !locale->builtin.casemap_full);
186 : : }
187 : :
188 : : static bool
189 : 0 : wc_iscased_builtin(pg_wchar wc, pg_locale_t locale)
190 : : {
191 : 0 : return pg_u_prop_cased(to_char32(wc));
192 : : }
193 : :
194 : : static pg_wchar
195 : 52 : wc_toupper_builtin(pg_wchar wc, pg_locale_t locale)
196 : : {
197 : 52 : return to_pg_wchar(unicode_uppercase_simple(to_char32(wc)));
198 : : }
199 : :
200 : : static pg_wchar
201 : 52 : wc_tolower_builtin(pg_wchar wc, pg_locale_t locale)
202 : : {
203 : 52 : return to_pg_wchar(unicode_lowercase_simple(to_char32(wc)));
204 : : }
205 : :
206 : : static const struct ctype_methods ctype_methods_builtin = {
207 : : .strlower = strlower_builtin,
208 : : .strtitle = strtitle_builtin,
209 : : .strupper = strupper_builtin,
210 : : .strfold = strfold_builtin,
211 : : /* uses plain ASCII semantics for historical reasons */
212 : : .downcase_ident = NULL,
213 : : .wc_isdigit = wc_isdigit_builtin,
214 : : .wc_isalpha = wc_isalpha_builtin,
215 : : .wc_isalnum = wc_isalnum_builtin,
216 : : .wc_isupper = wc_isupper_builtin,
217 : : .wc_islower = wc_islower_builtin,
218 : : .wc_isgraph = wc_isgraph_builtin,
219 : : .wc_isprint = wc_isprint_builtin,
220 : : .wc_ispunct = wc_ispunct_builtin,
221 : : .wc_isspace = wc_isspace_builtin,
222 : : .wc_isxdigit = wc_isxdigit_builtin,
223 : : .wc_iscased = wc_iscased_builtin,
224 : : .wc_tolower = wc_tolower_builtin,
225 : : .wc_toupper = wc_toupper_builtin,
226 : : };
227 : :
228 : : pg_locale_t
229 : 8 : create_pg_locale_builtin(Oid collid, MemoryContext context)
230 : : {
231 : 8 : const char *locstr;
232 : 8 : pg_locale_t result;
233 : :
234 [ - + ]: 8 : if (collid == DEFAULT_COLLATION_OID)
235 : : {
236 : 0 : HeapTuple tp;
237 : 0 : Datum datum;
238 : :
239 : 0 : tp = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
240 [ # # ]: 0 : if (!HeapTupleIsValid(tp))
241 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
242 : 0 : datum = SysCacheGetAttrNotNull(DATABASEOID, tp,
243 : : Anum_pg_database_datlocale);
244 : 0 : locstr = TextDatumGetCString(datum);
245 : 0 : ReleaseSysCache(tp);
246 : 0 : }
247 : : else
248 : : {
249 : 8 : HeapTuple tp;
250 : 8 : Datum datum;
251 : :
252 : 8 : tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
253 [ + - ]: 8 : if (!HeapTupleIsValid(tp))
254 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for collation %u", collid);
255 : 8 : datum = SysCacheGetAttrNotNull(COLLOID, tp,
256 : : Anum_pg_collation_colllocale);
257 : 8 : locstr = TextDatumGetCString(datum);
258 : 8 : ReleaseSysCache(tp);
259 : 8 : }
260 : :
261 : 8 : builtin_validate_locale(GetDatabaseEncoding(), locstr);
262 : :
263 : 8 : result = MemoryContextAllocZero(context, sizeof(struct pg_locale_struct));
264 : :
265 : 8 : result->builtin.locale = MemoryContextStrdup(context, locstr);
266 : 8 : result->builtin.casemap_full = (strcmp(locstr, "PG_UNICODE_FAST") == 0);
267 : 8 : result->deterministic = true;
268 : 8 : result->collate_is_c = true;
269 : 8 : result->ctype_is_c = (strcmp(locstr, "C") == 0);
270 [ + + ]: 8 : if (!result->ctype_is_c)
271 : 5 : result->ctype = &ctype_methods_builtin;
272 : :
273 : 16 : return result;
274 : 8 : }
275 : :
276 : : char *
277 : 13 : get_collation_actual_version_builtin(const char *collcollate)
278 : : {
279 : : /*
280 : : * The only two supported locales (C and C.UTF-8) are both based on memcmp
281 : : * and are not expected to change, but track the version anyway.
282 : : *
283 : : * Note that the character semantics may change for some locales, but the
284 : : * collation version only tracks changes to sort order.
285 : : */
286 [ + + ]: 13 : if (strcmp(collcollate, "C") == 0)
287 : 5 : return "1";
288 [ + + ]: 8 : else if (strcmp(collcollate, "C.UTF-8") == 0)
289 : 5 : return "1";
290 [ + - ]: 3 : else if (strcmp(collcollate, "PG_UNICODE_FAST") == 0)
291 : 3 : return "1";
292 : : else
293 [ # # # # ]: 0 : ereport(ERROR,
294 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
295 : : errmsg("invalid locale name \"%s\" for builtin provider",
296 : : collcollate)));
297 : :
298 : 0 : return NULL; /* keep compiler quiet */
299 : 13 : }
|