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