/src/postgres/src/backend/utils/adt/like.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * like.c |
4 | | * like expression handling code. |
5 | | * |
6 | | * NOTES |
7 | | * A big hack of the regexp.c code!! Contributed by |
8 | | * Keith Parks <emkxp01@mtcc.demon.co.uk> (7/95). |
9 | | * |
10 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
11 | | * Portions Copyright (c) 1994, Regents of the University of California |
12 | | * |
13 | | * IDENTIFICATION |
14 | | * src/backend/utils/adt/like.c |
15 | | * |
16 | | *------------------------------------------------------------------------- |
17 | | */ |
18 | | #include "postgres.h" |
19 | | |
20 | | #include <ctype.h> |
21 | | |
22 | | #include "catalog/pg_collation.h" |
23 | | #include "mb/pg_wchar.h" |
24 | | #include "miscadmin.h" |
25 | | #include "utils/fmgrprotos.h" |
26 | | #include "utils/pg_locale.h" |
27 | | #include "varatt.h" |
28 | | |
29 | | |
30 | 0 | #define LIKE_TRUE 1 |
31 | 0 | #define LIKE_FALSE 0 |
32 | 0 | #define LIKE_ABORT (-1) |
33 | | |
34 | | |
35 | | static int SB_MatchText(const char *t, int tlen, const char *p, int plen, |
36 | | pg_locale_t locale); |
37 | | static text *SB_do_like_escape(text *pat, text *esc); |
38 | | |
39 | | static int MB_MatchText(const char *t, int tlen, const char *p, int plen, |
40 | | pg_locale_t locale); |
41 | | static text *MB_do_like_escape(text *pat, text *esc); |
42 | | |
43 | | static int UTF8_MatchText(const char *t, int tlen, const char *p, int plen, |
44 | | pg_locale_t locale); |
45 | | |
46 | | static int C_IMatchText(const char *t, int tlen, const char *p, int plen, |
47 | | pg_locale_t locale); |
48 | | |
49 | | static int GenericMatchText(const char *s, int slen, const char *p, int plen, Oid collation); |
50 | | static int Generic_Text_IC_like(text *str, text *pat, Oid collation); |
51 | | |
52 | | /*-------------------- |
53 | | * Support routine for MatchText. Compares given multibyte streams |
54 | | * as wide characters. If they match, returns 1 otherwise returns 0. |
55 | | *-------------------- |
56 | | */ |
57 | | static inline int |
58 | | wchareq(const char *p1, int p1len, const char *p2, int p2len) |
59 | 0 | { |
60 | 0 | int p1clen; |
61 | | |
62 | | /* Optimization: quickly compare the first byte. */ |
63 | 0 | if (*p1 != *p2) |
64 | 0 | return 0; |
65 | | |
66 | 0 | p1clen = pg_mblen_with_len(p1, p1len); |
67 | 0 | if (pg_mblen_with_len(p2, p2len) != p1clen) |
68 | 0 | return 0; |
69 | | |
70 | | /* They are the same length */ |
71 | 0 | while (p1clen--) |
72 | 0 | { |
73 | 0 | if (*p1++ != *p2++) |
74 | 0 | return 0; |
75 | 0 | } |
76 | 0 | return 1; |
77 | 0 | } |
78 | | |
79 | | /* |
80 | | * Formerly we had a routine iwchareq() here that tried to do case-insensitive |
81 | | * comparison of multibyte characters. It did not work at all, however, |
82 | | * because it relied on tolower() which has a single-byte API ... and |
83 | | * towlower() wouldn't be much better since we have no suitably cheap way |
84 | | * of getting a single character transformed to the system's wchar_t format. |
85 | | * So now, we just downcase the strings using lower() and apply regular LIKE |
86 | | * comparison. This should be revisited when we install better locale support. |
87 | | * |
88 | | * We do handle case-insensitive matching for the C locale using |
89 | | * fold-on-the-fly processing, however. |
90 | | */ |
91 | | |
92 | | |
93 | 0 | #define NextByte(p, plen) ((p)++, (plen)--) |
94 | | |
95 | | /* Set up to compile like_match.c for multibyte characters */ |
96 | 0 | #define CHAREQ(p1, p1len, p2, p2len) wchareq((p1), (p1len), (p2), (p2len)) |
97 | | #define NextChar(p, plen) \ |
98 | 0 | do { int __l = pg_mblen_with_len((p), (plen)); (p) +=__l; (plen) -=__l; } while (0) |
99 | | #define CopyAdvChar(dst, src, srclen) \ |
100 | 0 | do { int __l = pg_mblen_with_len((src), (srclen)); \ |
101 | 0 | (srclen) -= __l; \ |
102 | 0 | while (__l-- > 0) \ |
103 | 0 | *(dst)++ = *(src)++; \ |
104 | 0 | } while (0) |
105 | | |
106 | 0 | #define MatchText MB_MatchText |
107 | | #define do_like_escape MB_do_like_escape |
108 | | |
109 | | #include "like_match.c" |
110 | | |
111 | | /* Set up to compile like_match.c for single-byte characters */ |
112 | 0 | #define CHAREQ(p1, p1len, p2, p2len) (*(p1) == *(p2)) |
113 | 0 | #define NextChar(p, plen) NextByte((p), (plen)) |
114 | 0 | #define CopyAdvChar(dst, src, srclen) (*(dst)++ = *(src)++, (srclen)--) |
115 | | |
116 | 0 | #define MatchText SB_MatchText |
117 | | #define do_like_escape SB_do_like_escape |
118 | | |
119 | | #include "like_match.c" |
120 | | |
121 | | /* setup to compile like_match.c for case-insensitive matches in C locale */ |
122 | | #define MATCH_LOWER |
123 | 0 | #define NextChar(p, plen) NextByte((p), (plen)) |
124 | 0 | #define MatchText C_IMatchText |
125 | | |
126 | | #include "like_match.c" |
127 | | |
128 | | /* setup to compile like_match.c for UTF8 encoding, using fast NextChar */ |
129 | | |
130 | | #define NextChar(p, plen) \ |
131 | 0 | do { (p)++; (plen)--; } while ((plen) > 0 && (*(p) & 0xC0) == 0x80 ) |
132 | 0 | #define MatchText UTF8_MatchText |
133 | | |
134 | | #include "like_match.c" |
135 | | |
136 | | /* Generic for all cases not requiring inline case-folding */ |
137 | | static inline int |
138 | | GenericMatchText(const char *s, int slen, const char *p, int plen, Oid collation) |
139 | 0 | { |
140 | 0 | pg_locale_t locale; |
141 | |
|
142 | 0 | if (!OidIsValid(collation)) |
143 | 0 | { |
144 | | /* |
145 | | * This typically means that the parser could not resolve a conflict |
146 | | * of implicit collations, so report it that way. |
147 | | */ |
148 | 0 | ereport(ERROR, |
149 | 0 | (errcode(ERRCODE_INDETERMINATE_COLLATION), |
150 | 0 | errmsg("could not determine which collation to use for LIKE"), |
151 | 0 | errhint("Use the COLLATE clause to set the collation explicitly."))); |
152 | 0 | } |
153 | | |
154 | 0 | locale = pg_newlocale_from_collation(collation); |
155 | |
|
156 | 0 | if (pg_database_encoding_max_length() == 1) |
157 | 0 | return SB_MatchText(s, slen, p, plen, locale); |
158 | 0 | else if (GetDatabaseEncoding() == PG_UTF8) |
159 | 0 | return UTF8_MatchText(s, slen, p, plen, locale); |
160 | 0 | else |
161 | 0 | return MB_MatchText(s, slen, p, plen, locale); |
162 | 0 | } |
163 | | |
164 | | static inline int |
165 | | Generic_Text_IC_like(text *str, text *pat, Oid collation) |
166 | 0 | { |
167 | 0 | char *s, |
168 | 0 | *p; |
169 | 0 | int slen, |
170 | 0 | plen; |
171 | 0 | pg_locale_t locale; |
172 | |
|
173 | 0 | if (!OidIsValid(collation)) |
174 | 0 | { |
175 | | /* |
176 | | * This typically means that the parser could not resolve a conflict |
177 | | * of implicit collations, so report it that way. |
178 | | */ |
179 | 0 | ereport(ERROR, |
180 | 0 | (errcode(ERRCODE_INDETERMINATE_COLLATION), |
181 | 0 | errmsg("could not determine which collation to use for ILIKE"), |
182 | 0 | errhint("Use the COLLATE clause to set the collation explicitly."))); |
183 | 0 | } |
184 | | |
185 | 0 | locale = pg_newlocale_from_collation(collation); |
186 | |
|
187 | 0 | if (!locale->deterministic) |
188 | 0 | ereport(ERROR, |
189 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
190 | 0 | errmsg("nondeterministic collations are not supported for ILIKE"))); |
191 | | |
192 | | /* |
193 | | * For efficiency reasons, in the C locale we don't call lower() on the |
194 | | * pattern and text, but instead lowercase each character lazily. This |
195 | | * only works for single-byte encodings, otherwise "_" may incorrectly |
196 | | * match an incomplete byte sequence. |
197 | | * |
198 | | * XXX: use casefolding instead? |
199 | | */ |
200 | | |
201 | 0 | if (locale->ctype_is_c && pg_database_encoding_max_length() == 1) |
202 | 0 | { |
203 | 0 | p = VARDATA_ANY(pat); |
204 | 0 | plen = VARSIZE_ANY_EXHDR(pat); |
205 | 0 | s = VARDATA_ANY(str); |
206 | 0 | slen = VARSIZE_ANY_EXHDR(str); |
207 | 0 | return C_IMatchText(s, slen, p, plen, locale); |
208 | 0 | } |
209 | 0 | else |
210 | 0 | { |
211 | 0 | pat = DatumGetTextPP(DirectFunctionCall1Coll(lower, collation, |
212 | 0 | PointerGetDatum(pat))); |
213 | 0 | p = VARDATA_ANY(pat); |
214 | 0 | plen = VARSIZE_ANY_EXHDR(pat); |
215 | 0 | str = DatumGetTextPP(DirectFunctionCall1Coll(lower, collation, |
216 | 0 | PointerGetDatum(str))); |
217 | 0 | s = VARDATA_ANY(str); |
218 | 0 | slen = VARSIZE_ANY_EXHDR(str); |
219 | |
|
220 | 0 | if (GetDatabaseEncoding() == PG_UTF8) |
221 | 0 | return UTF8_MatchText(s, slen, p, plen, 0); |
222 | 0 | else if (pg_database_encoding_max_length() > 1) |
223 | 0 | return MB_MatchText(s, slen, p, plen, 0); |
224 | 0 | else |
225 | 0 | return SB_MatchText(s, slen, p, plen, 0); |
226 | 0 | } |
227 | 0 | } |
228 | | |
229 | | /* |
230 | | * interface routines called by the function manager |
231 | | */ |
232 | | |
233 | | Datum |
234 | | namelike(PG_FUNCTION_ARGS) |
235 | 0 | { |
236 | 0 | Name str = PG_GETARG_NAME(0); |
237 | 0 | text *pat = PG_GETARG_TEXT_PP(1); |
238 | 0 | bool result; |
239 | 0 | char *s, |
240 | 0 | *p; |
241 | 0 | int slen, |
242 | 0 | plen; |
243 | |
|
244 | 0 | s = NameStr(*str); |
245 | 0 | slen = strlen(s); |
246 | 0 | p = VARDATA_ANY(pat); |
247 | 0 | plen = VARSIZE_ANY_EXHDR(pat); |
248 | |
|
249 | 0 | result = (GenericMatchText(s, slen, p, plen, PG_GET_COLLATION()) == LIKE_TRUE); |
250 | |
|
251 | 0 | PG_RETURN_BOOL(result); |
252 | 0 | } |
253 | | |
254 | | Datum |
255 | | namenlike(PG_FUNCTION_ARGS) |
256 | 0 | { |
257 | 0 | Name str = PG_GETARG_NAME(0); |
258 | 0 | text *pat = PG_GETARG_TEXT_PP(1); |
259 | 0 | bool result; |
260 | 0 | char *s, |
261 | 0 | *p; |
262 | 0 | int slen, |
263 | 0 | plen; |
264 | |
|
265 | 0 | s = NameStr(*str); |
266 | 0 | slen = strlen(s); |
267 | 0 | p = VARDATA_ANY(pat); |
268 | 0 | plen = VARSIZE_ANY_EXHDR(pat); |
269 | |
|
270 | 0 | result = (GenericMatchText(s, slen, p, plen, PG_GET_COLLATION()) != LIKE_TRUE); |
271 | |
|
272 | 0 | PG_RETURN_BOOL(result); |
273 | 0 | } |
274 | | |
275 | | Datum |
276 | | textlike(PG_FUNCTION_ARGS) |
277 | 0 | { |
278 | 0 | text *str = PG_GETARG_TEXT_PP(0); |
279 | 0 | text *pat = PG_GETARG_TEXT_PP(1); |
280 | 0 | bool result; |
281 | 0 | char *s, |
282 | 0 | *p; |
283 | 0 | int slen, |
284 | 0 | plen; |
285 | |
|
286 | 0 | s = VARDATA_ANY(str); |
287 | 0 | slen = VARSIZE_ANY_EXHDR(str); |
288 | 0 | p = VARDATA_ANY(pat); |
289 | 0 | plen = VARSIZE_ANY_EXHDR(pat); |
290 | |
|
291 | 0 | result = (GenericMatchText(s, slen, p, plen, PG_GET_COLLATION()) == LIKE_TRUE); |
292 | |
|
293 | 0 | PG_RETURN_BOOL(result); |
294 | 0 | } |
295 | | |
296 | | Datum |
297 | | textnlike(PG_FUNCTION_ARGS) |
298 | 0 | { |
299 | 0 | text *str = PG_GETARG_TEXT_PP(0); |
300 | 0 | text *pat = PG_GETARG_TEXT_PP(1); |
301 | 0 | bool result; |
302 | 0 | char *s, |
303 | 0 | *p; |
304 | 0 | int slen, |
305 | 0 | plen; |
306 | |
|
307 | 0 | s = VARDATA_ANY(str); |
308 | 0 | slen = VARSIZE_ANY_EXHDR(str); |
309 | 0 | p = VARDATA_ANY(pat); |
310 | 0 | plen = VARSIZE_ANY_EXHDR(pat); |
311 | |
|
312 | 0 | result = (GenericMatchText(s, slen, p, plen, PG_GET_COLLATION()) != LIKE_TRUE); |
313 | |
|
314 | 0 | PG_RETURN_BOOL(result); |
315 | 0 | } |
316 | | |
317 | | Datum |
318 | | bytealike(PG_FUNCTION_ARGS) |
319 | 0 | { |
320 | 0 | bytea *str = PG_GETARG_BYTEA_PP(0); |
321 | 0 | bytea *pat = PG_GETARG_BYTEA_PP(1); |
322 | 0 | bool result; |
323 | 0 | char *s, |
324 | 0 | *p; |
325 | 0 | int slen, |
326 | 0 | plen; |
327 | |
|
328 | 0 | s = VARDATA_ANY(str); |
329 | 0 | slen = VARSIZE_ANY_EXHDR(str); |
330 | 0 | p = VARDATA_ANY(pat); |
331 | 0 | plen = VARSIZE_ANY_EXHDR(pat); |
332 | |
|
333 | 0 | result = (SB_MatchText(s, slen, p, plen, 0) == LIKE_TRUE); |
334 | |
|
335 | 0 | PG_RETURN_BOOL(result); |
336 | 0 | } |
337 | | |
338 | | Datum |
339 | | byteanlike(PG_FUNCTION_ARGS) |
340 | 0 | { |
341 | 0 | bytea *str = PG_GETARG_BYTEA_PP(0); |
342 | 0 | bytea *pat = PG_GETARG_BYTEA_PP(1); |
343 | 0 | bool result; |
344 | 0 | char *s, |
345 | 0 | *p; |
346 | 0 | int slen, |
347 | 0 | plen; |
348 | |
|
349 | 0 | s = VARDATA_ANY(str); |
350 | 0 | slen = VARSIZE_ANY_EXHDR(str); |
351 | 0 | p = VARDATA_ANY(pat); |
352 | 0 | plen = VARSIZE_ANY_EXHDR(pat); |
353 | |
|
354 | 0 | result = (SB_MatchText(s, slen, p, plen, 0) != LIKE_TRUE); |
355 | |
|
356 | 0 | PG_RETURN_BOOL(result); |
357 | 0 | } |
358 | | |
359 | | /* |
360 | | * Case-insensitive versions |
361 | | */ |
362 | | |
363 | | Datum |
364 | | nameiclike(PG_FUNCTION_ARGS) |
365 | 0 | { |
366 | 0 | Name str = PG_GETARG_NAME(0); |
367 | 0 | text *pat = PG_GETARG_TEXT_PP(1); |
368 | 0 | bool result; |
369 | 0 | text *strtext; |
370 | |
|
371 | 0 | strtext = DatumGetTextPP(DirectFunctionCall1(name_text, |
372 | 0 | NameGetDatum(str))); |
373 | 0 | result = (Generic_Text_IC_like(strtext, pat, PG_GET_COLLATION()) == LIKE_TRUE); |
374 | |
|
375 | 0 | PG_RETURN_BOOL(result); |
376 | 0 | } |
377 | | |
378 | | Datum |
379 | | nameicnlike(PG_FUNCTION_ARGS) |
380 | 0 | { |
381 | 0 | Name str = PG_GETARG_NAME(0); |
382 | 0 | text *pat = PG_GETARG_TEXT_PP(1); |
383 | 0 | bool result; |
384 | 0 | text *strtext; |
385 | |
|
386 | 0 | strtext = DatumGetTextPP(DirectFunctionCall1(name_text, |
387 | 0 | NameGetDatum(str))); |
388 | 0 | result = (Generic_Text_IC_like(strtext, pat, PG_GET_COLLATION()) != LIKE_TRUE); |
389 | |
|
390 | 0 | PG_RETURN_BOOL(result); |
391 | 0 | } |
392 | | |
393 | | Datum |
394 | | texticlike(PG_FUNCTION_ARGS) |
395 | 0 | { |
396 | 0 | text *str = PG_GETARG_TEXT_PP(0); |
397 | 0 | text *pat = PG_GETARG_TEXT_PP(1); |
398 | 0 | bool result; |
399 | |
|
400 | 0 | result = (Generic_Text_IC_like(str, pat, PG_GET_COLLATION()) == LIKE_TRUE); |
401 | |
|
402 | 0 | PG_RETURN_BOOL(result); |
403 | 0 | } |
404 | | |
405 | | Datum |
406 | | texticnlike(PG_FUNCTION_ARGS) |
407 | 0 | { |
408 | 0 | text *str = PG_GETARG_TEXT_PP(0); |
409 | 0 | text *pat = PG_GETARG_TEXT_PP(1); |
410 | 0 | bool result; |
411 | |
|
412 | 0 | result = (Generic_Text_IC_like(str, pat, PG_GET_COLLATION()) != LIKE_TRUE); |
413 | |
|
414 | 0 | PG_RETURN_BOOL(result); |
415 | 0 | } |
416 | | |
417 | | /* |
418 | | * like_escape() --- given a pattern and an ESCAPE string, |
419 | | * convert the pattern to use Postgres' standard backslash escape convention. |
420 | | */ |
421 | | Datum |
422 | | like_escape(PG_FUNCTION_ARGS) |
423 | 0 | { |
424 | 0 | text *pat = PG_GETARG_TEXT_PP(0); |
425 | 0 | text *esc = PG_GETARG_TEXT_PP(1); |
426 | 0 | text *result; |
427 | |
|
428 | 0 | if (pg_database_encoding_max_length() == 1) |
429 | 0 | result = SB_do_like_escape(pat, esc); |
430 | 0 | else |
431 | 0 | result = MB_do_like_escape(pat, esc); |
432 | |
|
433 | 0 | PG_RETURN_TEXT_P(result); |
434 | 0 | } |
435 | | |
436 | | /* |
437 | | * like_escape_bytea() --- given a pattern and an ESCAPE string, |
438 | | * convert the pattern to use Postgres' standard backslash escape convention. |
439 | | */ |
440 | | Datum |
441 | | like_escape_bytea(PG_FUNCTION_ARGS) |
442 | 0 | { |
443 | 0 | bytea *pat = PG_GETARG_BYTEA_PP(0); |
444 | 0 | bytea *esc = PG_GETARG_BYTEA_PP(1); |
445 | 0 | bytea *result = SB_do_like_escape((text *) pat, (text *) esc); |
446 | |
|
447 | | PG_RETURN_BYTEA_P((bytea *) result); |
448 | 0 | } |