Branch data Line data Source code
1 : : /*
2 : : * contrib/pg_trgm/trgm_op.c
3 : : */
4 : : #include "postgres.h"
5 : :
6 : : #include <ctype.h>
7 : :
8 : : #include "trgm.h"
9 : :
10 : : #include "catalog/pg_type.h"
11 : : #include "tsearch/ts_locale.h"
12 : :
13 : :
14 : 2 : PG_MODULE_MAGIC;
15 : :
16 : : float4 trgm_limit = 0.3f;
17 : :
18 : 1 : PG_FUNCTION_INFO_V1(set_limit);
19 : : Datum set_limit(PG_FUNCTION_ARGS);
20 : :
21 : 1 : PG_FUNCTION_INFO_V1(show_limit);
22 : : Datum show_limit(PG_FUNCTION_ARGS);
23 : :
24 : 2 : PG_FUNCTION_INFO_V1(show_trgm);
25 : : Datum show_trgm(PG_FUNCTION_ARGS);
26 : :
27 : 2 : PG_FUNCTION_INFO_V1(similarity);
28 : : Datum similarity(PG_FUNCTION_ARGS);
29 : :
30 : 2 : PG_FUNCTION_INFO_V1(similarity_dist);
31 : : Datum similarity_dist(PG_FUNCTION_ARGS);
32 : :
33 : 2 : PG_FUNCTION_INFO_V1(similarity_op);
34 : : Datum similarity_op(PG_FUNCTION_ARGS);
35 : :
36 : :
37 : : Datum
38 : 0 : set_limit(PG_FUNCTION_ARGS)
39 : : {
40 : 0 : float4 nlimit = PG_GETARG_FLOAT4(0);
41 : :
42 [ # # ][ # # ]: 0 : if (nlimit < 0 || nlimit > 1.0)
43 : 0 : elog(ERROR, "wrong limit, should be between 0 and 1");
44 : 0 : trgm_limit = nlimit;
45 : 0 : PG_RETURN_FLOAT4(trgm_limit);
46 : : }
47 : :
48 : : Datum
49 : 0 : show_limit(PG_FUNCTION_ARGS)
50 : : {
51 : 0 : PG_RETURN_FLOAT4(trgm_limit);
52 : : }
53 : :
54 : : static int
55 : 1046792 : comp_trgm(const void *a, const void *b)
56 : : {
57 [ + + ][ + - ]: 1046792 : return CMPTRGM(a, b);
[ + + ][ + + ]
[ + - ][ + + ]
[ + + ][ + + ]
58 : : }
59 : :
60 : : static int
61 : 22710 : unique_array(trgm *a, int len)
62 : : {
63 : : trgm *curend,
64 : : *tmp;
65 : :
66 : 22710 : curend = tmp = a;
67 [ + + ]: 298985 : while (tmp - a < len)
68 [ + + ][ + + ]: 276275 : if (CMPTRGM(tmp, curend))
[ + + ]
69 : : {
70 : 252562 : curend++;
71 : 252562 : CPTRGM(curend, tmp);
72 : 252562 : tmp++;
73 : : }
74 : : else
75 : 276275 : tmp++;
76 : :
77 : 22710 : return curend + 1 - a;
78 : : }
79 : :
80 : : /*
81 : : * Finds first word in string, returns pointer to the word,
82 : : * endword points to the character after word
83 : : */
84 : : static char *
85 : 45397 : find_word(char *str, int lenstr, char **endword, int *charlen)
86 : : {
87 : 45397 : char *beginword = str;
88 : :
89 [ + + ][ + + ]: 45425 : while (beginword - str < lenstr && !ISWORDCHR(beginword))
[ + - ]
90 : 28 : beginword += pg_mblen(beginword);
91 : :
92 [ + + ]: 45397 : if (beginword - str >= lenstr)
93 : : return NULL;
94 : :
95 : 22703 : *endword = beginword;
96 : 22703 : *charlen = 0;
97 [ + + ][ + + ]: 276252 : while (*endword - str < lenstr && ISWORDCHR(*endword))
[ + + ]
98 : : {
99 : 253549 : *endword += pg_mblen(*endword);
100 : 253549 : (*charlen)++;
101 : : }
102 : :
103 : 45397 : return beginword;
104 : : }
105 : :
106 : : void
107 : 276589 : cnt_trigram(trgm *tptr, char *str, int bytelen)
108 : : {
109 [ + - ]: 276589 : if (bytelen == 3)
110 : : {
111 : 276589 : CPTRGM(tptr, str);
112 : : }
113 : : else
114 : : {
115 : : pg_crc32 crc;
116 : :
117 : 0 : INIT_CRC32(crc);
118 [ # # ]: 0 : COMP_CRC32(crc, str, bytelen);
119 : 0 : FIN_CRC32(crc);
120 : :
121 : : /*
122 : : * use only 3 upper bytes from crc, hope, it's good enough hashing
123 : : */
124 : 0 : CPTRGM(tptr, &crc);
125 : : }
126 : 276589 : }
127 : :
128 : : /*
129 : : * Adds trigrams from words (already padded).
130 : : */
131 : : static trgm *
132 : 22720 : make_trigrams(trgm *tptr, char *str, int bytelen, int charlen)
133 : : {
134 : 22720 : char *ptr = str;
135 : :
136 [ + - ]: 22720 : if (charlen < 3)
137 : : return tptr;
138 : :
139 : : #ifdef USE_WIDE_UPPER_LOWER
140 [ + - ]: 22720 : if (pg_database_encoding_max_length() > 1)
141 : : {
142 : 22720 : int lenfirst = pg_mblen(str),
143 : 22720 : lenmiddle = pg_mblen(str + lenfirst),
144 : 22720 : lenlast = pg_mblen(str + lenfirst + lenmiddle);
145 : :
146 [ + + ]: 298995 : while ((ptr - str) + lenfirst + lenmiddle + lenlast <= bytelen)
147 : : {
148 : 276275 : cnt_trigram(tptr, ptr, lenfirst + lenmiddle + lenlast);
149 : :
150 : 276275 : ptr += lenfirst;
151 : 276275 : tptr++;
152 : :
153 : 276275 : lenfirst = lenmiddle;
154 : 276275 : lenmiddle = lenlast;
155 : 276275 : lenlast = pg_mblen(ptr + lenfirst + lenmiddle);
156 : : }
157 : : }
158 : : else
159 : : #endif
160 : : {
161 [ # # ][ # # ]: 0 : Assert(bytelen == charlen);
162 : :
163 [ # # ]: 0 : while (ptr - str < bytelen - 2 /* number of trigrams = strlen - 2 */ )
164 : : {
165 : 0 : CPTRGM(tptr, ptr);
166 : 0 : ptr++;
167 : 0 : tptr++;
168 : : }
169 : : }
170 : :
171 : 22720 : return tptr;
172 : : }
173 : :
174 : : TRGM *
175 : 22695 : generate_trgm(char *str, int slen)
176 : : {
177 : : TRGM *trg;
178 : : char *buf;
179 : : trgm *tptr;
180 : : int len,
181 : : charlen,
182 : : bytelen;
183 : : char *bword,
184 : : *eword;
185 : :
186 : 22695 : trg = (TRGM *) palloc(TRGMHDRSIZE + sizeof(trgm) * (slen / 2 + 1) *3);
187 : 22695 : trg->flag = ARRKEY;
188 : 22695 : SET_VARSIZE(trg, TRGMHDRSIZE);
189 : :
190 [ + - ][ + + ]: 22695 : if (slen + LPADDING + RPADDING < 3 || slen == 0)
191 : : return trg;
192 : :
193 : 22694 : tptr = GETARR(trg);
194 : :
195 : 22694 : buf = palloc(sizeof(char) * (slen + 4));
196 : :
197 : : if (LPADDING > 0)
198 : : {
199 : 22694 : *buf = ' ';
200 : : if (LPADDING > 1)
201 : 22694 : *(buf + 1) = ' ';
202 : : }
203 : :
204 : 22694 : eword = str;
205 [ + + ]: 45397 : while ((bword = find_word(eword, slen - (eword - str), &eword, &charlen)) != NULL)
206 : : {
207 : : #ifdef IGNORECASE
208 : 22703 : bword = lowerstr_with_len(bword, eword - bword);
209 : 22703 : bytelen = strlen(bword);
210 : : #else
211 : : bytelen = eword - bword;
212 : : #endif
213 : :
214 : 22703 : memcpy(buf + LPADDING, bword, bytelen);
215 : :
216 : : #ifdef IGNORECASE
217 : 22703 : pfree(bword);
218 : : #endif
219 : 22703 : buf[LPADDING + bytelen] = ' ';
220 : 22703 : buf[LPADDING + bytelen + 1] = ' ';
221 : :
222 : : /*
223 : : * count trigrams
224 : : */
225 : 22703 : tptr = make_trigrams(tptr, buf, bytelen + LPADDING + RPADDING,
226 : : charlen + LPADDING + RPADDING);
227 : : }
228 : :
229 : 22694 : pfree(buf);
230 : :
231 [ + + ]: 22694 : if ((len = tptr - GETARR(trg)) == 0)
232 : : return trg;
233 : :
234 [ + - ]: 22693 : if (len > 0)
235 : : {
236 : 22693 : qsort((void *) GETARR(trg), len, sizeof(trgm), comp_trgm);
237 : 22693 : len = unique_array(GETARR(trg), len);
238 : : }
239 : :
240 : 22695 : SET_VARSIZE(trg, CALCGTSIZE(ARRKEY, len));
241 : :
242 : : return trg;
243 : : }
244 : :
245 : : /*
246 : : * Extract the next non-wildcard part of a search string, ie, a word bounded
247 : : * by '_' or '%' meta-characters, non-word characters or string end.
248 : : *
249 : : * str: source string, of length lenstr bytes (need not be null-terminated)
250 : : * buf: where to return the substring (must be long enough)
251 : : * *bytelen: receives byte length of the found substring
252 : : * *charlen: receives character length of the found substring
253 : : *
254 : : * Returns pointer to end+1 of the found substring in the source string.
255 : : * Returns NULL if no word found (in which case buf, bytelen, charlen not set)
256 : : *
257 : : * If the found word is bounded by non-word characters or string boundaries
258 : : * then this function will include corresponding padding spaces into buf.
259 : : */
260 : : static const char *
261 : 34 : get_wildcard_part(const char *str, int lenstr,
262 : : char *buf, int *bytelen, int *charlen)
263 : : {
264 : 34 : const char *beginword = str;
265 : : const char *endword;
266 : 34 : char *s = buf;
267 : 34 : bool in_leading_wildcard_meta = false;
268 : 34 : bool in_trailing_wildcard_meta = false;
269 : 34 : bool in_escape = false;
270 : : int clen;
271 : :
272 : : /*
273 : : * Find the first word character, remembering whether preceding character
274 : : * was wildcard meta-character. Note that the in_escape state persists
275 : : * from this loop to the next one, since we may exit at a word character
276 : : * that is in_escape.
277 : : */
278 [ + + ]: 68 : while (beginword - str < lenstr)
279 : : {
280 [ + + ]: 51 : if (in_escape)
281 : : {
282 [ - + ][ # # ]: 3 : if (ISWORDCHR(beginword))
283 : : break;
284 : : in_escape = false;
285 : : in_leading_wildcard_meta = false;
286 : : }
287 : : else
288 : : {
289 [ + + ]: 48 : if (ISESCAPECHAR(beginword))
290 : : in_escape = true;
291 [ + + ]: 45 : else if (ISWILDCARDCHAR(beginword))
292 : : in_leading_wildcard_meta = true;
293 [ - + ][ # # ]: 14 : else if (ISWORDCHR(beginword))
294 : : break;
295 : : else
296 : : in_leading_wildcard_meta = false;
297 : : }
298 : 34 : beginword += pg_mblen(beginword);
299 : : }
300 : :
301 : : /*
302 : : * Handle string end.
303 : : */
304 [ + + ]: 34 : if (beginword - str >= lenstr)
305 : : return NULL;
306 : :
307 : : /*
308 : : * Add left padding spaces if preceding character wasn't wildcard
309 : : * meta-character.
310 : : */
311 : 17 : *charlen = 0;
312 [ + + ]: 17 : if (!in_leading_wildcard_meta)
313 : : {
314 : : if (LPADDING > 0)
315 : : {
316 : 3 : *s++ = ' ';
317 : 3 : (*charlen)++;
318 : : if (LPADDING > 1)
319 : : {
320 : 3 : *s++ = ' ';
321 : 3 : (*charlen)++;
322 : : }
323 : : }
324 : : }
325 : :
326 : : /*
327 : : * Copy data into buf until wildcard meta-character, non-word character or
328 : : * string boundary. Strip escapes during copy.
329 : : */
330 : 17 : endword = beginword;
331 [ + - ]: 68 : while (endword - str < lenstr)
332 : : {
333 : 68 : clen = pg_mblen(endword);
334 [ + + ]: 68 : if (in_escape)
335 : : {
336 [ - + ][ # # ]: 3 : if (ISWORDCHR(endword))
337 : : {
338 : 3 : memcpy(s, endword, clen);
339 : 3 : (*charlen)++;
340 : 3 : s += clen;
341 : : }
342 : : else
343 : : {
344 : : /*
345 : : * Back up endword to the escape character when stopping at
346 : : * an escaped char, so that subsequent get_wildcard_part will
347 : : * restart from the escape character. We assume here that
348 : : * escape chars are single-byte.
349 : : */
350 : 0 : endword--;
351 : 0 : break;
352 : : }
353 : 3 : in_escape = false;
354 : : }
355 : : else
356 : : {
357 [ + - ]: 65 : if (ISESCAPECHAR(endword))
358 : : in_escape = true;
359 [ + + ]: 65 : else if (ISWILDCARDCHAR(endword))
360 : : {
361 : : in_trailing_wildcard_meta = true;
362 : : break;
363 : : }
364 [ - + ][ # # ]: 48 : else if (ISWORDCHR(endword))
365 : : {
366 : 48 : memcpy(s, endword, clen);
367 : 48 : (*charlen)++;
368 : 48 : s += clen;
369 : : }
370 : : else
371 : : break;
372 : : }
373 : 51 : endword += clen;
374 : : }
375 : :
376 : : /*
377 : : * Add right padding spaces if next character isn't wildcard
378 : : * meta-character.
379 : : */
380 [ - + ]: 17 : if (!in_trailing_wildcard_meta)
381 : : {
382 : : if (RPADDING > 0)
383 : : {
384 : 0 : *s++ = ' ';
385 : 0 : (*charlen)++;
386 : : if (RPADDING > 1)
387 : : {
388 : : *s++ = ' ';
389 : : (*charlen)++;
390 : : }
391 : : }
392 : : }
393 : :
394 : 17 : *bytelen = s - buf;
395 : 34 : return endword;
396 : : }
397 : :
398 : : /*
399 : : * Generates trigrams for wildcard search string.
400 : : *
401 : : * Returns array of trigrams that must occur in any string that matches the
402 : : * wildcard string. For example, given pattern "a%bcd%" the trigrams
403 : : * " a", "bcd" would be extracted.
404 : : */
405 : : TRGM *
406 : 17 : generate_wildcard_trgm(const char *str, int slen)
407 : : {
408 : : TRGM *trg;
409 : : char *buf,
410 : : *buf2;
411 : : trgm *tptr;
412 : : int len,
413 : : charlen,
414 : : bytelen;
415 : : const char *eword;
416 : :
417 : 17 : trg = (TRGM *) palloc(TRGMHDRSIZE + sizeof(trgm) * (slen / 2 + 1) *3);
418 : 17 : trg->flag = ARRKEY;
419 : 17 : SET_VARSIZE(trg, TRGMHDRSIZE);
420 : :
421 [ + - ][ + - ]: 17 : if (slen + LPADDING + RPADDING < 3 || slen == 0)
422 : : return trg;
423 : :
424 : 17 : tptr = GETARR(trg);
425 : :
426 : 17 : buf = palloc(sizeof(char) * (slen + 4));
427 : :
428 : : /*
429 : : * Extract trigrams from each substring extracted by get_wildcard_part.
430 : : */
431 : 17 : eword = str;
432 [ + + ]: 34 : while ((eword = get_wildcard_part(eword, slen - (eword - str),
433 : : buf, &bytelen, &charlen)) != NULL)
434 : : {
435 : : #ifdef IGNORECASE
436 : 17 : buf2 = lowerstr_with_len(buf, bytelen);
437 : 17 : bytelen = strlen(buf2);
438 : : #else
439 : : buf2 = buf;
440 : : #endif
441 : :
442 : : /*
443 : : * count trigrams
444 : : */
445 : 17 : tptr = make_trigrams(tptr, buf2, bytelen, charlen);
446 : : #ifdef IGNORECASE
447 : 17 : pfree(buf2);
448 : : #endif
449 : : }
450 : :
451 : 17 : pfree(buf);
452 : :
453 [ + - ]: 17 : if ((len = tptr - GETARR(trg)) == 0)
454 : : return trg;
455 : :
456 : : /*
457 : : * Make trigrams unique.
458 : : */
459 [ + - ]: 17 : if (len > 0)
460 : : {
461 : 17 : qsort((void *) GETARR(trg), len, sizeof(trgm), comp_trgm);
462 : 17 : len = unique_array(GETARR(trg), len);
463 : : }
464 : :
465 : 17 : SET_VARSIZE(trg, CALCGTSIZE(ARRKEY, len));
466 : :
467 : : return trg;
468 : : }
469 : :
470 : : uint32
471 : 12415 : trgm2int(trgm *ptr)
472 : : {
473 : 12415 : uint32 val = 0;
474 : :
475 : 12415 : val |= *(((unsigned char *) ptr));
476 : 12415 : val <<= 8;
477 : 12415 : val |= *(((unsigned char *) ptr) + 1);
478 : 12415 : val <<= 8;
479 : 12415 : val |= *(((unsigned char *) ptr) + 2);
480 : :
481 : 12415 : return val;
482 : : }
483 : :
484 : : Datum
485 : 7 : show_trgm(PG_FUNCTION_ARGS)
486 : : {
487 : 7 : text *in = PG_GETARG_TEXT_P(0);
488 : : TRGM *trg;
489 : : Datum *d;
490 : : ArrayType *a;
491 : : trgm *ptr;
492 : : int i;
493 : :
494 : 7 : trg = generate_trgm(VARDATA(in), VARSIZE(in) - VARHDRSZ);
495 : 7 : d = (Datum *) palloc(sizeof(Datum) * (1 + ARRNELEM(trg)));
496 : :
497 [ + + ]: 44 : for (i = 0, ptr = GETARR(trg); i < ARRNELEM(trg); i++, ptr++)
498 : : {
499 [ + - ]: 37 : text *item = (text *) palloc(VARHDRSZ + Max(12, pg_database_encoding_max_length() * 3));
500 : :
501 [ + - ][ + - ]: 37 : if (pg_database_encoding_max_length() > 1 && !ISPRINTABLETRGM(ptr))
[ + + ][ + - ]
[ + - ][ + + ]
[ + - ][ + - ]
[ + + ][ - + ]
502 : : {
503 : 0 : snprintf(VARDATA(item), 12, "0x%06x", trgm2int(ptr));
504 : 0 : SET_VARSIZE(item, VARHDRSZ + strlen(VARDATA(item)));
505 : : }
506 : : else
507 : : {
508 : 37 : SET_VARSIZE(item, VARHDRSZ + 3);
509 : 37 : CPTRGM(VARDATA(item), ptr);
510 : : }
511 : 37 : d[i] = PointerGetDatum(item);
512 : : }
513 : :
514 : 7 : a = construct_array(
515 : : d,
516 : : ARRNELEM(trg),
517 : : TEXTOID,
518 : : -1,
519 : : false,
520 : : 'i'
521 : : );
522 : :
523 [ + + ]: 44 : for (i = 0; i < ARRNELEM(trg); i++)
524 : 37 : pfree(DatumGetPointer(d[i]));
525 : :
526 : 7 : pfree(d);
527 : 7 : pfree(trg);
528 [ - + ]: 7 : PG_FREE_IF_COPY(in, 0);
529 : :
530 : 7 : PG_RETURN_POINTER(a);
531 : : }
532 : :
533 : : float4
534 : 14337 : cnt_sml(TRGM *trg1, TRGM *trg2)
535 : : {
536 : : trgm *ptr1,
537 : : *ptr2;
538 : 14337 : int count = 0;
539 : : int len1,
540 : : len2;
541 : :
542 : 14337 : ptr1 = GETARR(trg1);
543 : 14337 : ptr2 = GETARR(trg2);
544 : :
545 : 14337 : len1 = ARRNELEM(trg1);
546 : 14337 : len2 = ARRNELEM(trg2);
547 : :
548 [ + + ][ + + ]: 270957 : while (ptr1 - GETARR(trg1) < len1 && ptr2 - GETARR(trg2) < len2)
549 : : {
550 [ + + ][ + - ]: 256620 : int res = CMPTRGM(ptr1, ptr2);
[ + + ][ + + ]
[ + - ][ + + ]
[ + + ][ + + ]
551 : :
552 [ + + ]: 256620 : if (res < 0)
553 : 81585 : ptr1++;
554 [ + + ]: 175035 : else if (res > 0)
555 : 84603 : ptr2++;
556 : : else
557 : : {
558 : 90432 : ptr1++;
559 : 90432 : ptr2++;
560 : 256620 : count++;
561 : : }
562 : : }
563 : :
564 : : #ifdef DIVUNION
565 : 14337 : return ((((float4) count) / ((float4) (len1 + len2 - count))));
566 : : #else
567 : : return (((float) count) / ((float) ((len1 > len2) ? len1 : len2)));
568 : : #endif
569 : :
570 : : }
571 : :
572 : : /*
573 : : * Returns whether trg2 contains all trigrams in trg1.
574 : : * This relies on the trigram arrays being sorted.
575 : : */
576 : : bool
577 : 10 : trgm_contained_by(TRGM *trg1, TRGM *trg2)
578 : : {
579 : : trgm *ptr1,
580 : : *ptr2;
581 : : int len1,
582 : : len2;
583 : :
584 : 10 : ptr1 = GETARR(trg1);
585 : 10 : ptr2 = GETARR(trg2);
586 : :
587 : 10 : len1 = ARRNELEM(trg1);
588 : 10 : len2 = ARRNELEM(trg2);
589 : :
590 [ + + ][ + - ]: 43 : while (ptr1 - GETARR(trg1) < len1 && ptr2 - GETARR(trg2) < len2)
591 : : {
592 [ + + ][ + - ]: 38 : int res = CMPTRGM(ptr1, ptr2);
[ + + ][ + + ]
[ + - ][ - + ]
[ + + ][ + - ]
593 : :
594 [ + + ]: 38 : if (res < 0)
595 : : return false;
596 [ + + ]: 33 : else if (res > 0)
597 : 26 : ptr2++;
598 : : else
599 : : {
600 : 7 : ptr1++;
601 : 33 : ptr2++;
602 : : }
603 : : }
604 [ + - ]: 5 : if (ptr1 - GETARR(trg1) < len1)
605 : : return false;
606 : : else
607 : 10 : return true;
608 : : }
609 : :
610 : : Datum
611 : 10337 : similarity(PG_FUNCTION_ARGS)
612 : : {
613 : 10337 : text *in1 = PG_GETARG_TEXT_P(0);
614 : 10337 : text *in2 = PG_GETARG_TEXT_P(1);
615 : : TRGM *trg1,
616 : : *trg2;
617 : : float4 res;
618 : :
619 : 10337 : trg1 = generate_trgm(VARDATA(in1), VARSIZE(in1) - VARHDRSZ);
620 : 10337 : trg2 = generate_trgm(VARDATA(in2), VARSIZE(in2) - VARHDRSZ);
621 : :
622 : 10337 : res = cnt_sml(trg1, trg2);
623 : :
624 : 10337 : pfree(trg1);
625 : 10337 : pfree(trg2);
626 [ + + ]: 10337 : PG_FREE_IF_COPY(in1, 0);
627 [ - + ]: 10337 : PG_FREE_IF_COPY(in2, 1);
628 : :
629 : 10337 : PG_RETURN_FLOAT4(res);
630 : : }
631 : :
632 : : Datum
633 : 1002 : similarity_dist(PG_FUNCTION_ARGS)
634 : : {
635 : 1002 : float4 res = DatumGetFloat4(DirectFunctionCall2(similarity,
636 : : PG_GETARG_DATUM(0),
637 : : PG_GETARG_DATUM(1)));
638 : :
639 : 1002 : PG_RETURN_FLOAT4(1.0 - res);
640 : : }
641 : :
642 : : Datum
643 : 6000 : similarity_op(PG_FUNCTION_ARGS)
644 : : {
645 : 6000 : float4 res = DatumGetFloat4(DirectFunctionCall2(similarity,
646 : : PG_GETARG_DATUM(0),
647 : : PG_GETARG_DATUM(1)));
648 : :
649 [ + + ]: 6000 : PG_RETURN_BOOL(res >= trgm_limit);
650 : : }
|