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
264 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);
138 52 : 753 : bool curr_alnum = pg_u_isalnum(u, wbstate->posix);
53 : :
264 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 : 6005 : strlower_builtin(char *dest, size_t destsize, const char *src, ssize_t srclen,
72 : : pg_locale_t locale)
73 : : {
232 74 : 12010 : return unicode_strlower(dest, destsize, src, srclen,
75 : 6005 : locale->info.builtin.casemap_full);
76 : : }
77 : :
78 : : static size_t
264 79 : 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,
138 86 : 97 : .posix = !locale->info.builtin.casemap_full,
87 : : .init = false,
88 : : .prev_alnum = false,
89 : : };
90 : :
232 91 : 194 : return unicode_strtitle(dest, destsize, src, srclen,
92 : 97 : locale->info.builtin.casemap_full,
93 : : initcap_wbnext, &wbstate);
94 : : }
95 : :
96 : : static size_t
264 97 : 158441 : strupper_builtin(char *dest, size_t destsize, const char *src, ssize_t srclen,
98 : : pg_locale_t locale)
99 : : {
232 100 : 316882 : return unicode_strupper(dest, destsize, src, srclen,
101 : 158441 : locale->info.builtin.casemap_full);
102 : : }
103 : :
104 : : static size_t
225 105 : 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,
109 : 6 : locale->info.builtin.casemap_full);
110 : : }
111 : :
112 : : static bool
67 jdavis@postgresql.or 113 :GNC 32826 : wc_isdigit_builtin(pg_wchar wc, pg_locale_t locale)
114 : : {
115 : 32826 : return pg_u_isdigit(wc, !locale->info.builtin.casemap_full);
116 : : }
117 : :
118 : : static bool
119 : 2059 : wc_isalpha_builtin(pg_wchar wc, pg_locale_t locale)
120 : : {
121 : 2059 : return pg_u_isalpha(wc);
122 : : }
123 : :
124 : : static bool
125 : 18430 : wc_isalnum_builtin(pg_wchar wc, pg_locale_t locale)
126 : : {
127 : 18430 : return pg_u_isalnum(wc, !locale->info.builtin.casemap_full);
128 : : }
129 : :
130 : : static bool
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
67 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
67 jdavis@postgresql.or 155 :GNC 12288 : wc_ispunct_builtin(pg_wchar wc, pg_locale_t locale)
156 : : {
157 : 12288 : return pg_u_ispunct(wc, !locale->info.builtin.casemap_full);
158 : : }
159 : :
160 : : static bool
161 : 8199 : wc_isspace_builtin(pg_wchar wc, pg_locale_t locale)
162 : : {
163 : 8199 : return pg_u_isspace(wc);
164 : : }
165 : :
166 : : static bool
67 jdavis@postgresql.or 167 :UNC 0 : char_is_cased_builtin(char ch, pg_locale_t locale)
168 : : {
169 [ # # ]: 0 : return IS_HIGHBIT_SET(ch) ||
170 [ # # # # : 0 : (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
# # # # ]
171 : : }
172 : :
173 : : static pg_wchar
67 jdavis@postgresql.or 174 :GNC 264 : wc_toupper_builtin(pg_wchar wc, pg_locale_t locale)
175 : : {
176 : 264 : return unicode_uppercase_simple(wc);
177 : : }
178 : :
179 : : static pg_wchar
180 : 264 : wc_tolower_builtin(pg_wchar wc, pg_locale_t locale)
181 : : {
182 : 264 : return unicode_lowercase_simple(wc);
183 : : }
184 : :
185 : : static const struct ctype_methods ctype_methods_builtin = {
186 : : .strlower = strlower_builtin,
187 : : .strtitle = strtitle_builtin,
188 : : .strupper = strupper_builtin,
189 : : .strfold = strfold_builtin,
190 : : .wc_isdigit = wc_isdigit_builtin,
191 : : .wc_isalpha = wc_isalpha_builtin,
192 : : .wc_isalnum = wc_isalnum_builtin,
193 : : .wc_isupper = wc_isupper_builtin,
194 : : .wc_islower = wc_islower_builtin,
195 : : .wc_isgraph = wc_isgraph_builtin,
196 : : .wc_isprint = wc_isprint_builtin,
197 : : .wc_ispunct = wc_ispunct_builtin,
198 : : .wc_isspace = wc_isspace_builtin,
199 : : .char_is_cased = char_is_cased_builtin,
200 : : .wc_tolower = wc_tolower_builtin,
201 : : .wc_toupper = wc_toupper_builtin,
202 : : };
203 : :
204 : : pg_locale_t
278 jdavis@postgresql.or 205 :CBC 889 : create_pg_locale_builtin(Oid collid, MemoryContext context)
206 : : {
207 : : const char *locstr;
208 : : pg_locale_t result;
209 : :
210 [ + + ]: 889 : if (collid == DEFAULT_COLLATION_OID)
211 : : {
212 : : HeapTuple tp;
213 : : Datum datum;
214 : :
215 : 863 : tp = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
216 [ - + ]: 863 : if (!HeapTupleIsValid(tp))
278 jdavis@postgresql.or 217 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
278 jdavis@postgresql.or 218 :CBC 863 : datum = SysCacheGetAttrNotNull(DATABASEOID, tp,
219 : : Anum_pg_database_datlocale);
220 : 863 : locstr = TextDatumGetCString(datum);
221 : 863 : ReleaseSysCache(tp);
222 : : }
223 : : else
224 : : {
225 : : HeapTuple tp;
226 : : Datum datum;
227 : :
228 : 26 : tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
229 [ - + ]: 26 : if (!HeapTupleIsValid(tp))
278 jdavis@postgresql.or 230 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for collation %u", collid);
278 jdavis@postgresql.or 231 :CBC 26 : datum = SysCacheGetAttrNotNull(COLLOID, tp,
232 : : Anum_pg_collation_colllocale);
233 : 26 : locstr = TextDatumGetCString(datum);
234 : 26 : ReleaseSysCache(tp);
235 : : }
236 : :
237 : 889 : builtin_validate_locale(GetDatabaseEncoding(), locstr);
238 : :
239 : 889 : result = MemoryContextAllocZero(context, sizeof(struct pg_locale_struct));
240 : :
241 : 889 : result->info.builtin.locale = MemoryContextStrdup(context, locstr);
232 242 : 889 : result->info.builtin.casemap_full = (strcmp(locstr, "PG_UNICODE_FAST") == 0);
278 243 : 889 : result->deterministic = true;
244 : 889 : result->collate_is_c = true;
245 : 889 : result->ctype_is_c = (strcmp(locstr, "C") == 0);
67 jdavis@postgresql.or 246 [ + + ]:GNC 889 : if (!result->ctype_is_c)
247 : 878 : result->ctype = &ctype_methods_builtin;
248 : :
278 jdavis@postgresql.or 249 :CBC 889 : return result;
250 : : }
251 : :
252 : : char *
241 253 : 920 : get_collation_actual_version_builtin(const char *collcollate)
254 : : {
255 : : /*
256 : : * The only two supported locales (C and C.UTF-8) are both based on memcmp
257 : : * and are not expected to change, but track the version anyway.
258 : : *
259 : : * Note that the character semantics may change for some locales, but the
260 : : * collation version only tracks changes to sort order.
261 : : */
262 [ + + ]: 920 : if (strcmp(collcollate, "C") == 0)
263 : 24 : return "1";
264 [ + + ]: 896 : else if (strcmp(collcollate, "C.UTF-8") == 0)
265 : 886 : return "1";
232 266 [ + - ]: 10 : else if (strcmp(collcollate, "PG_UNICODE_FAST") == 0)
267 : 10 : return "1";
268 : : else
241 jdavis@postgresql.or 269 [ # # ]:UBC 0 : ereport(ERROR,
270 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
271 : : errmsg("invalid locale name \"%s\" for builtin provider",
272 : : collcollate)));
273 : :
274 : : return NULL; /* keep compiler quiet */
275 : : }
|