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