/src/postgres/src/backend/tsearch/ts_locale.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * ts_locale.c |
4 | | * locale compatibility layer for tsearch |
5 | | * |
6 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
7 | | * |
8 | | * |
9 | | * IDENTIFICATION |
10 | | * src/backend/tsearch/ts_locale.c |
11 | | * |
12 | | *------------------------------------------------------------------------- |
13 | | */ |
14 | | #include "postgres.h" |
15 | | |
16 | | #include "common/string.h" |
17 | | #include "storage/fd.h" |
18 | | #include "tsearch/ts_locale.h" |
19 | | |
20 | | static void tsearch_readline_callback(void *arg); |
21 | | |
22 | | |
23 | | /* space for a single character plus a trailing NUL */ |
24 | | #define WC_BUF_LEN 2 |
25 | | |
26 | | #define GENERATE_T_ISCLASS_DEF(character_class) \ |
27 | | /* mblen shall be that of the first character */ \ |
28 | | int \ |
29 | 0 | t_is##character_class##_with_len(const char *ptr, int mblen) \ |
30 | 0 | { \ |
31 | 0 | pg_wchar wstr[WC_BUF_LEN]; \ |
32 | 0 | int wlen pg_attribute_unused(); \ |
33 | 0 | wlen = pg_mb2wchar_with_len(ptr, wstr, mblen); \ |
34 | 0 | Assert(wlen <= 1); \ |
35 | 0 | /* pass single character, or NUL if empty */ \ |
36 | 0 | return pg_isw##character_class(wstr[0], pg_database_locale()); \ |
37 | 0 | } \ |
38 | | \ |
39 | | /* ptr shall point to a NUL-terminated string */ \ |
40 | | int \ |
41 | 0 | t_is##character_class##_cstr(const char *ptr) \ |
42 | 0 | { \ |
43 | 0 | return t_is##character_class##_with_len(ptr, pg_mblen_cstr(ptr)); \ |
44 | 0 | } \ Unexecuted instantiation: t_isalnum_cstr Unexecuted instantiation: t_isalpha_cstr |
45 | | /* ptr shall point to a string with pre-validated encoding */ \ |
46 | | int \ |
47 | 0 | t_is##character_class##_unbounded(const char *ptr) \ |
48 | 0 | { \ |
49 | 0 | return t_is##character_class##_with_len(ptr, pg_mblen_unbounded(ptr)); \ |
50 | 0 | } \ Unexecuted instantiation: t_isalnum_unbounded Unexecuted instantiation: t_isalpha_unbounded |
51 | | /* historical name for _unbounded */ \ |
52 | | int \ |
53 | 0 | t_is##character_class(const char *ptr) \ |
54 | 0 | { \ |
55 | 0 | return t_is##character_class##_unbounded(ptr); \ |
56 | 0 | } Unexecuted instantiation: t_isalnum Unexecuted instantiation: t_isalpha |
57 | | |
58 | 0 | GENERATE_T_ISCLASS_DEF(alnum) |
59 | 0 | GENERATE_T_ISCLASS_DEF(alpha) |
60 | | |
61 | | /* |
62 | | * Set up to read a file using tsearch_readline(). This facility is |
63 | | * better than just reading the file directly because it provides error |
64 | | * context pointing to the specific line where a problem is detected. |
65 | | * |
66 | | * Expected usage is: |
67 | | * |
68 | | * tsearch_readline_state trst; |
69 | | * |
70 | | * if (!tsearch_readline_begin(&trst, filename)) |
71 | | * ereport(ERROR, |
72 | | * (errcode(ERRCODE_CONFIG_FILE_ERROR), |
73 | | * errmsg("could not open stop-word file \"%s\": %m", |
74 | | * filename))); |
75 | | * while ((line = tsearch_readline(&trst)) != NULL) |
76 | | * process line; |
77 | | * tsearch_readline_end(&trst); |
78 | | * |
79 | | * Note that the caller supplies the ereport() for file open failure; |
80 | | * this is so that a custom message can be provided. The filename string |
81 | | * passed to tsearch_readline_begin() must remain valid through |
82 | | * tsearch_readline_end(). |
83 | | */ |
84 | | bool |
85 | | tsearch_readline_begin(tsearch_readline_state *stp, |
86 | | const char *filename) |
87 | 0 | { |
88 | 0 | if ((stp->fp = AllocateFile(filename, "r")) == NULL) |
89 | 0 | return false; |
90 | 0 | stp->filename = filename; |
91 | 0 | stp->lineno = 0; |
92 | 0 | initStringInfo(&stp->buf); |
93 | 0 | stp->curline = NULL; |
94 | | /* Setup error traceback support for ereport() */ |
95 | 0 | stp->cb.callback = tsearch_readline_callback; |
96 | 0 | stp->cb.arg = stp; |
97 | 0 | stp->cb.previous = error_context_stack; |
98 | 0 | error_context_stack = &stp->cb; |
99 | 0 | return true; |
100 | 0 | } |
101 | | |
102 | | /* |
103 | | * Read the next line from a tsearch data file (expected to be in UTF-8), and |
104 | | * convert it to database encoding if needed. The returned string is palloc'd. |
105 | | * NULL return means EOF. |
106 | | */ |
107 | | char * |
108 | | tsearch_readline(tsearch_readline_state *stp) |
109 | 0 | { |
110 | 0 | char *recoded; |
111 | | |
112 | | /* Advance line number to use in error reports */ |
113 | 0 | stp->lineno++; |
114 | | |
115 | | /* Clear curline, it's no longer relevant */ |
116 | 0 | if (stp->curline) |
117 | 0 | { |
118 | 0 | if (stp->curline != stp->buf.data) |
119 | 0 | pfree(stp->curline); |
120 | 0 | stp->curline = NULL; |
121 | 0 | } |
122 | | |
123 | | /* Collect next line, if there is one */ |
124 | 0 | if (!pg_get_line_buf(stp->fp, &stp->buf)) |
125 | 0 | return NULL; |
126 | | |
127 | | /* Validate the input as UTF-8, then convert to DB encoding if needed */ |
128 | 0 | recoded = pg_any_to_server(stp->buf.data, stp->buf.len, PG_UTF8); |
129 | | |
130 | | /* Save the correctly-encoded string for possible error reports */ |
131 | 0 | stp->curline = recoded; /* might be equal to buf.data */ |
132 | | |
133 | | /* |
134 | | * We always return a freshly pstrdup'd string. This is clearly necessary |
135 | | * if pg_any_to_server() returned buf.data, and we need a second copy even |
136 | | * if encoding conversion did occur. The caller is entitled to pfree the |
137 | | * returned string at any time, which would leave curline pointing to |
138 | | * recycled storage, causing problems if an error occurs after that point. |
139 | | * (It's preferable to return the result of pstrdup instead of the output |
140 | | * of pg_any_to_server, because the conversion result tends to be |
141 | | * over-allocated. Since callers might save the result string directly |
142 | | * into a long-lived dictionary structure, we don't want it to be a larger |
143 | | * palloc chunk than necessary. We'll reclaim the conversion result on |
144 | | * the next call.) |
145 | | */ |
146 | 0 | return pstrdup(recoded); |
147 | 0 | } |
148 | | |
149 | | /* |
150 | | * Close down after reading a file with tsearch_readline() |
151 | | */ |
152 | | void |
153 | | tsearch_readline_end(tsearch_readline_state *stp) |
154 | 0 | { |
155 | | /* Suppress use of curline in any error reported below */ |
156 | 0 | if (stp->curline) |
157 | 0 | { |
158 | 0 | if (stp->curline != stp->buf.data) |
159 | 0 | pfree(stp->curline); |
160 | 0 | stp->curline = NULL; |
161 | 0 | } |
162 | | |
163 | | /* Release other resources */ |
164 | 0 | pfree(stp->buf.data); |
165 | 0 | FreeFile(stp->fp); |
166 | | |
167 | | /* Pop the error context stack */ |
168 | 0 | error_context_stack = stp->cb.previous; |
169 | 0 | } |
170 | | |
171 | | /* |
172 | | * Error context callback for errors occurring while reading a tsearch |
173 | | * configuration file. |
174 | | */ |
175 | | static void |
176 | | tsearch_readline_callback(void *arg) |
177 | 0 | { |
178 | 0 | tsearch_readline_state *stp = (tsearch_readline_state *) arg; |
179 | | |
180 | | /* |
181 | | * We can't include the text of the config line for errors that occur |
182 | | * during tsearch_readline() itself. The major cause of such errors is |
183 | | * encoding violations, and we daren't try to print error messages |
184 | | * containing badly-encoded data. |
185 | | */ |
186 | 0 | if (stp->curline) |
187 | 0 | errcontext("line %d of configuration file \"%s\": \"%s\"", |
188 | 0 | stp->lineno, |
189 | 0 | stp->filename, |
190 | 0 | stp->curline); |
191 | 0 | else |
192 | 0 | errcontext("line %d of configuration file \"%s\"", |
193 | 0 | stp->lineno, |
194 | 0 | stp->filename); |
195 | 0 | } |