Age Owner Branch data TLA Line data Source code
1 : : /*--------------------------------------------------------------------------
2 : : *
3 : : * test_regex.c
4 : : * Test harness for the regular expression package.
5 : : *
6 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : * IDENTIFICATION
10 : : * src/test/modules/test_regex/test_regex.c
11 : : *
12 : : * -------------------------------------------------------------------------
13 : : */
14 : :
15 : : #include "postgres.h"
16 : :
17 : : #include "funcapi.h"
18 : : #include "regex/regex.h"
19 : : #include "utils/array.h"
20 : : #include "utils/builtins.h"
21 : :
1806 tgl@sss.pgh.pa.us 22 :CBC 2 : PG_MODULE_MAGIC;
23 : :
24 : :
25 : : /* all the options of interest for regex functions */
26 : : typedef struct test_re_flags
27 : : {
28 : : int cflags; /* compile flags for Spencer's regex code */
29 : : int eflags; /* execute flags for Spencer's regex code */
30 : : long info; /* expected re_info bits */
31 : : bool glob; /* do it globally (for each occurrence) */
32 : : bool indices; /* report indices not actual strings */
33 : : bool partial; /* expect partial match */
34 : : } test_re_flags;
35 : :
36 : : /* cross-call state for test_regex() */
37 : : typedef struct test_regex_ctx
38 : : {
39 : : test_re_flags re_flags; /* flags */
40 : : rm_detail_t details; /* "details" from execution */
41 : : text *orig_str; /* data string in original TEXT form */
42 : : int nmatches; /* number of places where pattern matched */
43 : : int npatterns; /* number of capturing subpatterns */
44 : : /* We store start char index and end+1 char index for each match */
45 : : /* so the number of entries in match_locs is nmatches * npatterns * 2 */
46 : : int *match_locs; /* 0-based character indexes */
47 : : int next_match; /* 0-based index of next match to process */
48 : : /* workspace for build_test_match_result() */
49 : : Datum *elems; /* has npatterns+1 elements */
50 : : bool *nulls; /* has npatterns+1 elements */
51 : : pg_wchar *wide_str; /* wide-char version of original string */
52 : : char *conv_buf; /* conversion buffer, if needed */
53 : : int conv_bufsiz; /* size thereof */
54 : : } test_regex_ctx;
55 : :
56 : : /* Local functions */
57 : : static void test_re_compile(text *text_re, int cflags, Oid collation,
58 : : regex_t *result_re);
59 : : static void parse_test_flags(test_re_flags *flags, text *opts);
60 : : static test_regex_ctx *setup_test_matches(text *orig_str,
61 : : regex_t *cpattern,
62 : : test_re_flags *re_flags,
63 : : Oid collation,
64 : : bool use_subpatterns);
65 : : static ArrayType *build_test_info_result(regex_t *cpattern,
66 : : test_re_flags *flags);
67 : : static ArrayType *build_test_match_result(test_regex_ctx *matchctx);
68 : :
69 : :
70 : : /*
71 : : * test_regex(pattern text, string text, flags text) returns setof text[]
72 : : *
73 : : * This is largely based on regexp.c's regexp_matches, with additions
74 : : * for debugging purposes.
75 : : */
76 : 3 : PG_FUNCTION_INFO_V1(test_regex);
77 : :
78 : : Datum
79 : 1767 : test_regex(PG_FUNCTION_ARGS)
80 : : {
81 : : FuncCallContext *funcctx;
82 : : test_regex_ctx *matchctx;
83 : : ArrayType *result_ary;
84 : :
85 [ + + ]: 1767 : if (SRF_IS_FIRSTCALL())
86 : : {
87 : 696 : text *pattern = PG_GETARG_TEXT_PP(0);
88 : 696 : text *flags = PG_GETARG_TEXT_PP(2);
89 : 696 : Oid collation = PG_GET_COLLATION();
90 : : test_re_flags re_flags;
91 : : regex_t cpattern;
92 : : MemoryContext oldcontext;
93 : :
94 : 696 : funcctx = SRF_FIRSTCALL_INIT();
95 : 696 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
96 : :
97 : : /* Determine options */
98 : 696 : parse_test_flags(&re_flags, flags);
99 : :
100 : : /* set up the compiled pattern */
101 : 696 : test_re_compile(pattern, re_flags.cflags, collation, &cpattern);
102 : :
103 : : /* be sure to copy the input string into the multi-call ctx */
104 : 590 : matchctx = setup_test_matches(PG_GETARG_TEXT_P_COPY(1), &cpattern,
105 : : &re_flags,
106 : : collation,
107 : : true);
108 : :
109 : : /* Pre-create workspace that build_test_match_result needs */
8 michael@paquier.xyz 110 :GNC 590 : matchctx->elems = palloc_array(Datum, matchctx->npatterns + 1);
111 : 590 : matchctx->nulls = palloc_array(bool, matchctx->npatterns + 1);
112 : :
1806 tgl@sss.pgh.pa.us 113 :CBC 590 : MemoryContextSwitchTo(oldcontext);
384 peter@eisentraut.org 114 : 590 : funcctx->user_fctx = matchctx;
115 : :
116 : : /*
117 : : * Return the first result row, which is info equivalent to Tcl's
118 : : * "regexp -about" output
119 : : */
1806 tgl@sss.pgh.pa.us 120 : 590 : result_ary = build_test_info_result(&cpattern, &re_flags);
121 : :
122 : 590 : pg_regfree(&cpattern);
123 : :
124 : 590 : SRF_RETURN_NEXT(funcctx, PointerGetDatum(result_ary));
125 : : }
126 : : else
127 : : {
128 : : /* Each subsequent row describes one match */
129 : 1071 : funcctx = SRF_PERCALL_SETUP();
130 : 1071 : matchctx = (test_regex_ctx *) funcctx->user_fctx;
131 : :
132 [ + + ]: 1071 : if (matchctx->next_match < matchctx->nmatches)
133 : : {
134 : 481 : result_ary = build_test_match_result(matchctx);
135 : 481 : matchctx->next_match++;
136 : 481 : SRF_RETURN_NEXT(funcctx, PointerGetDatum(result_ary));
137 : : }
138 : : }
139 : :
140 : 590 : SRF_RETURN_DONE(funcctx);
141 : : }
142 : :
143 : :
144 : : /*
145 : : * test_re_compile - compile a RE
146 : : *
147 : : * text_re --- the pattern, expressed as a TEXT object
148 : : * cflags --- compile options for the pattern
149 : : * collation --- collation to use for LC_CTYPE-dependent behavior
150 : : * result_re --- output, compiled RE is stored here
151 : : *
152 : : * Pattern is given in the database encoding. We internally convert to
153 : : * an array of pg_wchar, which is what Spencer's regex package wants.
154 : : *
155 : : * Caller must eventually pg_regfree the resulting RE to avoid memory leaks.
156 : : */
157 : : static void
158 : 696 : test_re_compile(text *text_re, int cflags, Oid collation,
159 : : regex_t *result_re)
160 : : {
161 [ - + - - : 696 : int text_re_len = VARSIZE_ANY_EXHDR(text_re);
- - - - -
+ ]
162 [ - + ]: 696 : char *text_re_val = VARDATA_ANY(text_re);
163 : : pg_wchar *pattern;
164 : : int pattern_len;
165 : : int regcomp_result;
166 : : char errMsg[100];
167 : :
168 : : /* Convert pattern string to wide characters */
169 : 696 : pattern = (pg_wchar *) palloc((text_re_len + 1) * sizeof(pg_wchar));
170 : 696 : pattern_len = pg_mb2wchar_with_len(text_re_val,
171 : : pattern,
172 : : text_re_len);
173 : :
174 : 696 : regcomp_result = pg_regcomp(result_re,
175 : : pattern,
176 : : pattern_len,
177 : : cflags,
178 : : collation);
179 : :
180 : 696 : pfree(pattern);
181 : :
182 [ + + ]: 696 : if (regcomp_result != REG_OKAY)
183 : : {
184 : : /* re didn't compile (no need for pg_regfree, if so) */
185 : 106 : pg_regerror(regcomp_result, result_re, errMsg, sizeof(errMsg));
186 [ + - ]: 106 : ereport(ERROR,
187 : : (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
188 : : errmsg("invalid regular expression: %s", errMsg)));
189 : : }
190 : 590 : }
191 : :
192 : : /*
193 : : * test_re_execute - execute a RE on pg_wchar data
194 : : *
195 : : * Returns true on match, false on no match
196 : : * Arguments are as for pg_regexec
197 : : */
198 : : static bool
199 : 590 : test_re_execute(regex_t *re, pg_wchar *data, int data_len,
200 : : int start_search,
201 : : rm_detail_t *details,
202 : : int nmatch, regmatch_t *pmatch,
203 : : int eflags)
204 : : {
205 : : int regexec_result;
206 : : char errMsg[100];
207 : :
208 : : /* Initialize match locations in case engine doesn't */
209 : 590 : details->rm_extend.rm_so = -1;
210 : 590 : details->rm_extend.rm_eo = -1;
211 [ + + ]: 1466 : for (int i = 0; i < nmatch; i++)
212 : : {
213 : 876 : pmatch[i].rm_so = -1;
214 : 876 : pmatch[i].rm_eo = -1;
215 : : }
216 : :
217 : : /* Perform RE match and return result */
218 : 590 : regexec_result = pg_regexec(re,
219 : : data,
220 : : data_len,
221 : : start_search,
222 : : details,
223 : : nmatch,
224 : : pmatch,
225 : : eflags);
226 : :
227 [ + + - + ]: 590 : if (regexec_result != REG_OKAY && regexec_result != REG_NOMATCH)
228 : : {
229 : : /* re failed??? */
1806 tgl@sss.pgh.pa.us 230 :UBC 0 : pg_regerror(regexec_result, re, errMsg, sizeof(errMsg));
231 [ # # ]: 0 : ereport(ERROR,
232 : : (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
233 : : errmsg("regular expression failed: %s", errMsg)));
234 : : }
235 : :
1806 tgl@sss.pgh.pa.us 236 :CBC 590 : return (regexec_result == REG_OKAY);
237 : : }
238 : :
239 : :
240 : : /*
241 : : * parse_test_flags - parse the flags argument
242 : : *
243 : : * flags --- output argument, filled with desired options
244 : : * opts --- TEXT object, or NULL for defaults
245 : : */
246 : : static void
247 : 696 : parse_test_flags(test_re_flags *flags, text *opts)
248 : : {
249 : : /* these defaults must match Tcl's */
250 : 696 : int cflags = REG_ADVANCED;
251 : 696 : int eflags = 0;
252 : 696 : long info = 0;
253 : :
254 : 696 : flags->glob = false;
255 : 696 : flags->indices = false;
256 : 696 : flags->partial = false;
257 : :
258 [ + - ]: 696 : if (opts)
259 : : {
260 [ - + ]: 696 : char *opt_p = VARDATA_ANY(opts);
261 [ - + - - : 696 : int opt_len = VARSIZE_ANY_EXHDR(opts);
- - - - -
+ ]
262 : : int i;
263 : :
264 [ + + ]: 1891 : for (i = 0; i < opt_len; i++)
265 : : {
266 [ + + + + : 1195 : switch (opt_p[i])
- + + + +
+ + + + +
+ + + + -
- - - + +
+ + + + +
+ + + + +
+ + + + +
+ - ]
267 : : {
268 : 78 : case '-':
269 : : /* allowed, no-op */
270 : 78 : break;
271 : 7 : case '!':
272 : 7 : flags->partial = true;
273 : 7 : break;
274 : 1 : case '*':
275 : : /* test requires Unicode --- ignored here */
276 : 1 : break;
277 : 53 : case '0':
278 : 53 : flags->indices = true;
279 : 53 : break;
280 : :
281 : : /* These flags correspond to user-exposed RE options: */
1806 tgl@sss.pgh.pa.us 282 :UBC 0 : case 'g': /* global match */
283 : 0 : flags->glob = true;
284 : 0 : break;
1806 tgl@sss.pgh.pa.us 285 :CBC 20 : case 'i': /* case insensitive */
286 : 20 : cflags |= REG_ICASE;
287 : 20 : break;
288 : 35 : case 'n': /* \n affects ^ $ . [^ */
289 : 35 : cflags |= REG_NEWLINE;
290 : 35 : break;
291 : 2 : case 'p': /* ~Perl, \n affects . [^ */
292 : 2 : cflags |= REG_NLSTOP;
293 : 2 : cflags &= ~REG_NLANCH;
294 : 2 : break;
295 : 2 : case 'w': /* weird, \n affects ^ $ only */
296 : 2 : cflags &= ~REG_NLSTOP;
297 : 2 : cflags |= REG_NLANCH;
298 : 2 : break;
299 : 14 : case 'x': /* expanded syntax */
300 : 14 : cflags |= REG_EXPANDED;
301 : 14 : break;
302 : :
303 : : /* These flags correspond to Tcl's -xflags options: */
304 : 2 : case 'a':
305 : 2 : cflags |= REG_ADVF;
306 : 2 : break;
307 : 131 : case 'b':
308 : 131 : cflags &= ~REG_ADVANCED;
309 : 131 : break;
310 : 11 : case 'c':
311 : :
312 : : /*
313 : : * Tcl calls this TCL_REG_CANMATCH, but it's really
314 : : * REG_EXPECT. In this implementation we must also set
315 : : * the partial and indices flags, so that
316 : : * setup_test_matches and build_test_match_result will
317 : : * emit the desired data. (They'll emit more fields than
318 : : * Tcl would, but that's fine.)
319 : : */
320 : 11 : cflags |= REG_EXPECT;
321 : 11 : flags->partial = true;
322 : 11 : flags->indices = true;
323 : 11 : break;
324 : 10 : case 'e':
325 : 10 : cflags &= ~REG_ADVANCED;
326 : 10 : cflags |= REG_EXTENDED;
327 : 10 : break;
328 : 6 : case 'q':
329 : 6 : cflags &= ~REG_ADVANCED;
330 : 6 : cflags |= REG_QUOTE;
331 : 6 : break;
332 : 2 : case 'o': /* o for opaque */
333 : 2 : cflags |= REG_NOSUB;
334 : 2 : break;
335 : 2 : case 's': /* s for start */
336 : 2 : cflags |= REG_BOSONLY;
337 : 2 : break;
338 : 6 : case '+':
339 : 6 : cflags |= REG_FAKE;
340 : 6 : break;
1806 tgl@sss.pgh.pa.us 341 :UBC 0 : case ',':
342 : 0 : cflags |= REG_PROGRESS;
343 : 0 : break;
344 : 0 : case '.':
345 : 0 : cflags |= REG_DUMP;
346 : 0 : break;
347 : 0 : case ':':
348 : 0 : eflags |= REG_MTRACE;
349 : 0 : break;
350 : 0 : case ';':
351 : 0 : eflags |= REG_FTRACE;
352 : 0 : break;
1806 tgl@sss.pgh.pa.us 353 :CBC 6 : case '^':
354 : 6 : eflags |= REG_NOTBOL;
355 : 6 : break;
356 : 4 : case '$':
357 : 4 : eflags |= REG_NOTEOL;
358 : 4 : break;
359 : 17 : case 't':
360 : 17 : cflags |= REG_EXPECT;
361 : 17 : break;
362 : 5 : case '%':
363 : 5 : eflags |= REG_SMALL;
364 : 5 : break;
365 : :
366 : : /* These flags define expected info bits: */
367 : 5 : case 'A':
368 : 5 : info |= REG_UBSALNUM;
369 : 5 : break;
370 : 4 : case 'B':
371 : 4 : info |= REG_UBRACES;
372 : 4 : break;
373 : 42 : case 'E':
374 : 42 : info |= REG_UBBS;
375 : 42 : break;
376 : 34 : case 'H':
377 : 34 : info |= REG_ULOOKAROUND;
378 : 34 : break;
379 : 11 : case 'I':
380 : 11 : info |= REG_UIMPOSSIBLE;
381 : 11 : break;
382 : 164 : case 'L':
383 : 164 : info |= REG_ULOCALE;
384 : 164 : break;
385 : 43 : case 'M':
386 : 43 : info |= REG_UUNPORT;
387 : 43 : break;
388 : 47 : case 'N':
389 : 47 : info |= REG_UEMPTYMATCH;
390 : 47 : break;
391 : 307 : case 'P':
392 : 307 : info |= REG_UNONPOSIX;
393 : 307 : break;
394 : 36 : case 'Q':
395 : 36 : info |= REG_UBOUNDS;
396 : 36 : break;
397 : 42 : case 'R':
398 : 42 : info |= REG_UBACKREF;
399 : 42 : break;
400 : 25 : case 'S':
401 : 25 : info |= REG_UUNSPEC;
402 : 25 : break;
403 : 20 : case 'T':
404 : 20 : info |= REG_USHORTEST;
405 : 20 : break;
406 : 1 : case 'U':
407 : 1 : info |= REG_UPBOTCH;
408 : 1 : break;
409 : :
1806 tgl@sss.pgh.pa.us 410 :UBC 0 : default:
411 [ # # ]: 0 : ereport(ERROR,
412 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
413 : : errmsg("invalid regular expression test option: \"%.*s\"",
414 : : pg_mblen(opt_p + i), opt_p + i)));
415 : : break;
416 : : }
417 : : }
418 : : }
1806 tgl@sss.pgh.pa.us 419 :CBC 696 : flags->cflags = cflags;
420 : 696 : flags->eflags = eflags;
421 : 696 : flags->info = info;
422 : 696 : }
423 : :
424 : : /*
425 : : * setup_test_matches --- do the initial matching
426 : : *
427 : : * To simplify memory management, we do all the matching in one swoop.
428 : : * The returned test_regex_ctx contains the locations of all the substrings
429 : : * matching the pattern.
430 : : */
431 : : static test_regex_ctx *
432 : 590 : setup_test_matches(text *orig_str,
433 : : regex_t *cpattern, test_re_flags *re_flags,
434 : : Oid collation,
435 : : bool use_subpatterns)
436 : : {
8 michael@paquier.xyz 437 :GNC 590 : test_regex_ctx *matchctx = palloc0_object(test_regex_ctx);
1806 tgl@sss.pgh.pa.us 438 :CBC 590 : int eml = pg_database_encoding_max_length();
439 : : int orig_len;
440 : : pg_wchar *wide_str;
441 : : int wide_len;
442 : : regmatch_t *pmatch;
443 : : int pmatch_len;
444 : : int array_len;
445 : : int array_idx;
446 : : int prev_match_end;
447 : : int start_search;
448 : 590 : int maxlen = 0; /* largest fetch length in characters */
449 : :
450 : : /* save flags */
451 : 590 : matchctx->re_flags = *re_flags;
452 : :
453 : : /* save original string --- we'll extract result substrings from it */
454 : 590 : matchctx->orig_str = orig_str;
455 : :
456 : : /* convert string to pg_wchar form for matching */
457 [ - + - - : 590 : orig_len = VARSIZE_ANY_EXHDR(orig_str);
- - - - -
+ ]
8 michael@paquier.xyz 458 :GNC 590 : wide_str = palloc_array(pg_wchar, orig_len + 1);
1806 tgl@sss.pgh.pa.us 459 [ - + ]:CBC 590 : wide_len = pg_mb2wchar_with_len(VARDATA_ANY(orig_str), wide_str, orig_len);
460 : :
461 : : /* do we want to remember subpatterns? */
462 [ + - + + ]: 590 : if (use_subpatterns && cpattern->re_nsub > 0)
463 : : {
464 : 127 : matchctx->npatterns = cpattern->re_nsub + 1;
465 : 127 : pmatch_len = cpattern->re_nsub + 1;
466 : : }
467 : : else
468 : : {
469 : 463 : use_subpatterns = false;
470 : 463 : matchctx->npatterns = 1;
471 : 463 : pmatch_len = 1;
472 : : }
473 : :
474 : : /* temporary output space for RE package */
8 michael@paquier.xyz 475 :GNC 590 : pmatch = palloc_array(regmatch_t, pmatch_len);
476 : :
477 : : /*
478 : : * the real output space (grown dynamically if needed)
479 : : *
480 : : * use values 2^n-1, not 2^n, so that we hit the limit at 2^28-1 rather
481 : : * than at 2^27
482 : : */
1806 tgl@sss.pgh.pa.us 483 [ - + ]:CBC 590 : array_len = re_flags->glob ? 255 : 31;
8 michael@paquier.xyz 484 :GNC 590 : matchctx->match_locs = palloc_array(int, array_len);
1806 tgl@sss.pgh.pa.us 485 :CBC 590 : array_idx = 0;
486 : :
487 : : /* search for the pattern, perhaps repeatedly */
488 : 590 : prev_match_end = 0;
489 : 590 : start_search = 0;
490 [ + + ]: 590 : while (test_re_execute(cpattern, wide_str, wide_len,
491 : : start_search,
492 : : &matchctx->details,
493 : : pmatch_len, pmatch,
494 : : re_flags->eflags))
495 : : {
496 : : /* enlarge output space if needed */
497 [ - + ]: 463 : while (array_idx + matchctx->npatterns * 2 + 1 > array_len)
498 : : {
1806 tgl@sss.pgh.pa.us 499 :UBC 0 : array_len += array_len + 1; /* 2^n-1 => 2^(n+1)-1 */
500 [ # # ]: 0 : if (array_len > MaxAllocSize / sizeof(int))
501 [ # # ]: 0 : ereport(ERROR,
502 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
503 : : errmsg("too many regular expression matches")));
504 : 0 : matchctx->match_locs = (int *) repalloc(matchctx->match_locs,
505 : : sizeof(int) * array_len);
506 : : }
507 : :
508 : : /* save this match's locations */
1806 tgl@sss.pgh.pa.us 509 [ + + ]:CBC 1094 : for (int i = 0; i < matchctx->npatterns; i++)
510 : : {
511 : 631 : int so = pmatch[i].rm_so;
512 : 631 : int eo = pmatch[i].rm_eo;
513 : :
514 : 631 : matchctx->match_locs[array_idx++] = so;
515 : 631 : matchctx->match_locs[array_idx++] = eo;
516 [ + + + - : 631 : if (so >= 0 && eo >= 0 && (eo - so) > maxlen)
+ + ]
517 : 438 : maxlen = (eo - so);
518 : : }
519 : 463 : matchctx->nmatches++;
520 : 463 : prev_match_end = pmatch[0].rm_eo;
521 : :
522 : : /* if not glob, stop after one match */
523 [ + - ]: 463 : if (!re_flags->glob)
524 : 463 : break;
525 : :
526 : : /*
527 : : * Advance search position. Normally we start the next search at the
528 : : * end of the previous match; but if the match was of zero length, we
529 : : * have to advance by one character, or we'd just find the same match
530 : : * again.
531 : : */
1806 tgl@sss.pgh.pa.us 532 :UBC 0 : start_search = prev_match_end;
533 [ # # ]: 0 : if (pmatch[0].rm_so == pmatch[0].rm_eo)
534 : 0 : start_search++;
535 [ # # ]: 0 : if (start_search > wide_len)
536 : 0 : break;
537 : : }
538 : :
539 : : /*
540 : : * If we had no match, but "partial" and "indices" are set, emit the
541 : : * details.
542 : : */
1806 tgl@sss.pgh.pa.us 543 [ + + + + :CBC 590 : if (matchctx->nmatches == 0 && re_flags->partial && re_flags->indices)
+ - ]
544 : : {
545 : : /* enlarge output space if needed */
1795 546 [ - + ]: 18 : while (array_idx + matchctx->npatterns * 2 + 1 > array_len)
547 : : {
1795 tgl@sss.pgh.pa.us 548 :UBC 0 : array_len += array_len + 1; /* 2^n-1 => 2^(n+1)-1 */
549 [ # # ]: 0 : if (array_len > MaxAllocSize / sizeof(int))
550 [ # # ]: 0 : ereport(ERROR,
551 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
552 : : errmsg("too many regular expression matches")));
553 : 0 : matchctx->match_locs = (int *) repalloc(matchctx->match_locs,
554 : : sizeof(int) * array_len);
555 : : }
556 : :
1806 tgl@sss.pgh.pa.us 557 :CBC 18 : matchctx->match_locs[array_idx++] = matchctx->details.rm_extend.rm_so;
558 : 18 : matchctx->match_locs[array_idx++] = matchctx->details.rm_extend.rm_eo;
559 : : /* we don't have pmatch data, so emit -1 */
560 [ + + ]: 20 : for (int i = 1; i < matchctx->npatterns; i++)
561 : : {
562 : 2 : matchctx->match_locs[array_idx++] = -1;
563 : 2 : matchctx->match_locs[array_idx++] = -1;
564 : : }
565 : 18 : matchctx->nmatches++;
566 : : }
567 : :
1795 568 [ - + ]: 590 : Assert(array_idx <= array_len);
569 : :
1806 570 [ + - ]: 590 : if (eml > 1)
571 : : {
572 : 590 : int64 maxsiz = eml * (int64) maxlen;
573 : : int conv_bufsiz;
574 : :
575 : : /*
576 : : * Make the conversion buffer large enough for any substring of
577 : : * interest.
578 : : *
579 : : * Worst case: assume we need the maximum size (maxlen*eml), but take
580 : : * advantage of the fact that the original string length in bytes is
581 : : * an upper bound on the byte length of any fetched substring (and we
582 : : * know that len+1 is safe to allocate because the varlena header is
583 : : * longer than 1 byte).
584 : : */
585 [ + + ]: 590 : if (maxsiz > orig_len)
586 : 415 : conv_bufsiz = orig_len + 1;
587 : : else
588 : 175 : conv_bufsiz = maxsiz + 1; /* safe since maxsiz < 2^30 */
589 : :
590 : 590 : matchctx->conv_buf = palloc(conv_bufsiz);
591 : 590 : matchctx->conv_bufsiz = conv_bufsiz;
592 : 590 : matchctx->wide_str = wide_str;
593 : : }
594 : : else
595 : : {
596 : : /* No need to keep the wide string if we're in a single-byte charset. */
1806 tgl@sss.pgh.pa.us 597 :UBC 0 : pfree(wide_str);
598 : 0 : matchctx->wide_str = NULL;
599 : 0 : matchctx->conv_buf = NULL;
600 : 0 : matchctx->conv_bufsiz = 0;
601 : : }
602 : :
603 : : /* Clean up temp storage */
1806 tgl@sss.pgh.pa.us 604 :CBC 590 : pfree(pmatch);
605 : :
606 : 590 : return matchctx;
607 : : }
608 : :
609 : : /*
610 : : * build_test_info_result - build output array describing compiled regexp
611 : : *
612 : : * This borrows some code from Tcl's TclRegAbout().
613 : : */
614 : : static ArrayType *
615 : 590 : build_test_info_result(regex_t *cpattern, test_re_flags *flags)
616 : : {
617 : : /* Translation data for flag bits in regex_t.re_info */
618 : : struct infoname
619 : : {
620 : : int bit;
621 : : const char *text;
622 : : };
623 : : static const struct infoname infonames[] = {
624 : : {REG_UBACKREF, "REG_UBACKREF"},
625 : : {REG_ULOOKAROUND, "REG_ULOOKAROUND"},
626 : : {REG_UBOUNDS, "REG_UBOUNDS"},
627 : : {REG_UBRACES, "REG_UBRACES"},
628 : : {REG_UBSALNUM, "REG_UBSALNUM"},
629 : : {REG_UPBOTCH, "REG_UPBOTCH"},
630 : : {REG_UBBS, "REG_UBBS"},
631 : : {REG_UNONPOSIX, "REG_UNONPOSIX"},
632 : : {REG_UUNSPEC, "REG_UUNSPEC"},
633 : : {REG_UUNPORT, "REG_UUNPORT"},
634 : : {REG_ULOCALE, "REG_ULOCALE"},
635 : : {REG_UEMPTYMATCH, "REG_UEMPTYMATCH"},
636 : : {REG_UIMPOSSIBLE, "REG_UIMPOSSIBLE"},
637 : : {REG_USHORTEST, "REG_USHORTEST"},
638 : : {0, NULL}
639 : : };
640 : : const struct infoname *inf;
641 : : Datum elems[lengthof(infonames) + 1];
642 : 590 : int nresults = 0;
643 : : char buf[80];
644 : : int dims[1];
645 : : int lbs[1];
646 : :
647 : : /* Set up results: first, the number of subexpressions */
648 : 590 : snprintf(buf, sizeof(buf), "%d", (int) cpattern->re_nsub);
649 : 590 : elems[nresults++] = PointerGetDatum(cstring_to_text(buf));
650 : :
651 : : /* Report individual info bit states */
652 [ + + ]: 8850 : for (inf = infonames; inf->bit != 0; inf++)
653 : : {
654 [ + + ]: 8260 : if (cpattern->re_info & inf->bit)
655 : : {
656 [ + - ]: 758 : if (flags->info & inf->bit)
657 : 758 : elems[nresults++] = PointerGetDatum(cstring_to_text(inf->text));
658 : : else
659 : : {
1806 tgl@sss.pgh.pa.us 660 :UBC 0 : snprintf(buf, sizeof(buf), "unexpected %s!", inf->text);
661 : 0 : elems[nresults++] = PointerGetDatum(cstring_to_text(buf));
662 : : }
663 : : }
664 : : else
665 : : {
1806 tgl@sss.pgh.pa.us 666 [ - + ]:CBC 7502 : if (flags->info & inf->bit)
667 : : {
1806 tgl@sss.pgh.pa.us 668 :UBC 0 : snprintf(buf, sizeof(buf), "missing %s!", inf->text);
669 : 0 : elems[nresults++] = PointerGetDatum(cstring_to_text(buf));
670 : : }
671 : : }
672 : : }
673 : :
674 : : /* And form an array */
1806 tgl@sss.pgh.pa.us 675 :CBC 590 : dims[0] = nresults;
676 : 590 : lbs[0] = 1;
677 : : /* XXX: this hardcodes assumptions about the text type */
678 : 590 : return construct_md_array(elems, NULL, 1, dims, lbs,
679 : : TEXTOID, -1, false, TYPALIGN_INT);
680 : : }
681 : :
682 : : /*
683 : : * build_test_match_result - build output array for current match
684 : : *
685 : : * Note that if the indices flag is set, we don't need any strings,
686 : : * just the location data.
687 : : */
688 : : static ArrayType *
689 : 481 : build_test_match_result(test_regex_ctx *matchctx)
690 : : {
691 : 481 : char *buf = matchctx->conv_buf;
692 : 481 : Datum *elems = matchctx->elems;
693 : 481 : bool *nulls = matchctx->nulls;
694 : 481 : bool indices = matchctx->re_flags.indices;
695 : : char bufstr[80];
696 : : int dims[1];
697 : : int lbs[1];
698 : : int loc;
699 : : int i;
700 : :
701 : : /* Extract matching substrings from the original string */
702 : 481 : loc = matchctx->next_match * matchctx->npatterns * 2;
703 [ + + ]: 1132 : for (i = 0; i < matchctx->npatterns; i++)
704 : : {
705 : 651 : int so = matchctx->match_locs[loc++];
706 : 651 : int eo = matchctx->match_locs[loc++];
707 : :
708 [ + + ]: 651 : if (indices)
709 : : {
710 : : /* Report eo this way for consistency with Tcl */
711 [ + + ]: 84 : snprintf(bufstr, sizeof(bufstr), "%d %d",
712 : : so, so < 0 ? eo : eo - 1);
713 : 84 : elems[i] = PointerGetDatum(cstring_to_text(bufstr));
714 : 84 : nulls[i] = false;
715 : : }
716 [ + + - + ]: 567 : else if (so < 0 || eo < 0)
717 : : {
718 : 12 : elems[i] = (Datum) 0;
719 : 12 : nulls[i] = true;
720 : : }
721 [ + - ]: 555 : else if (buf)
722 : : {
723 : 555 : int len = pg_wchar2mb_with_len(matchctx->wide_str + so,
724 : : buf,
725 : : eo - so);
726 : :
727 [ - + ]: 555 : Assert(len < matchctx->conv_bufsiz);
728 : 555 : elems[i] = PointerGetDatum(cstring_to_text_with_len(buf, len));
729 : 555 : nulls[i] = false;
730 : : }
731 : : else
732 : : {
1806 tgl@sss.pgh.pa.us 733 :UBC 0 : elems[i] = DirectFunctionCall3(text_substr,
734 : : PointerGetDatum(matchctx->orig_str),
735 : : Int32GetDatum(so + 1),
736 : : Int32GetDatum(eo - so));
737 : 0 : nulls[i] = false;
738 : : }
739 : : }
740 : :
741 : : /* In EXPECT indices mode, also report the "details" */
1806 tgl@sss.pgh.pa.us 742 [ + + + + ]:CBC 481 : if (indices && (matchctx->re_flags.cflags & REG_EXPECT))
743 : : {
744 : 28 : int so = matchctx->details.rm_extend.rm_so;
745 : 28 : int eo = matchctx->details.rm_extend.rm_eo;
746 : :
747 [ + + ]: 28 : snprintf(bufstr, sizeof(bufstr), "%d %d",
748 : : so, so < 0 ? eo : eo - 1);
749 : 28 : elems[i] = PointerGetDatum(cstring_to_text(bufstr));
750 : 28 : nulls[i] = false;
751 : 28 : i++;
752 : : }
753 : :
754 : : /* And form an array */
755 : 481 : dims[0] = i;
756 : 481 : lbs[0] = 1;
757 : : /* XXX: this hardcodes assumptions about the text type */
758 : 481 : return construct_md_array(elems, nulls, 1, dims, lbs,
759 : : TEXTOID, -1, false, TYPALIGN_INT);
760 : : }
|