/*------------------------------------------------------------------------- * * regexp.c * Postgres' interface to the regular expression package. * * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $PostgreSQL: pgsql/src/backend/utils/adt/regexp.c,v 1.71 2007/03/28 22:59:37 neilc Exp $ * * Alistair Crooks added the code for the regex caching * agc - cached the regular expressions used - there's a good chance * that we'll get a hit, so this saves a compile step for every * attempted match. I haven't actually measured the speed improvement, * but it `looks' a lot quicker visually when watching regression * test output. * * agc - incorporated Keith Bostic's Berkeley regex code into * the tree for all ports. To distinguish this regex code from any that * is existent on a platform, I've prepended the string "pg_" to * the functions regcomp, regerror, regexec and regfree. * Fixed a bug that was originally a typo by me, where `i' was used * instead of `oldest' when compiling regular expressions - benign * results mostly, although occasionally it bit you... * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/heapam.h" #include "catalog/pg_type.h" #include "funcapi.h" #include "regex/regex.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/lsyscache.h" /* GUC-settable flavor parameter */ static int regex_flavor = REG_ADVANCED; /* * We cache precompiled regular expressions using a "self organizing list" * structure, in which recently-used items tend to be near the front. * Whenever we use an entry, it's moved up to the front of the list. * Over time, an item's average position corresponds to its frequency of use. * * When we first create an entry, it's inserted at the front of * the array, dropping the entry at the end of the array if necessary to * make room. (This might seem to be weighting the new entry too heavily, * but if we insert new entries further back, we'll be unable to adjust to * a sudden shift in the query mix where we are presented with MAX_CACHED_RES * never-before-seen items used circularly. We ought to be able to handle * that case, so we have to insert at the front.) * * Knuth mentions a variant strategy in which a used item is moved up just * one place in the list. Although he says this uses fewer comparisons on * average, it seems not to adapt very well to the situation where you have * both some reusable patterns and a steady stream of non-reusable patterns. * A reusable pattern that isn't used at least as often as non-reusable * patterns are seen will "fail to keep up" and will drop off the end of the * cache. With move-to-front, a reusable pattern is guaranteed to stay in * the cache as long as it's used at least once in every MAX_CACHED_RES uses. */ /* this is the maximum number of cached regular expressions */ #ifndef MAX_CACHED_RES #define MAX_CACHED_RES 32 #endif /* this structure describes one cached regular expression */ typedef struct cached_re_str { text *cre_pat; /* original RE (untoasted TEXT form) */ int cre_flags; /* compile flags: extended,icase etc */ regex_t cre_re; /* the compiled regular expression */ } cached_re_str; typedef struct re_comp_flags { int cflags; bool glob; } re_comp_flags; typedef struct regexp_matches_ctx { text *orig_str; size_t orig_len; pg_wchar *wide_str; size_t wide_len; regex_t *cpattern; regmatch_t *pmatch; size_t offset; re_comp_flags flags; } regexp_matches_ctx; typedef struct regexp_split_ctx { text *orig_str; size_t orig_len; pg_wchar *wide_str; size_t wide_len; regex_t *cpattern; regmatch_t match; size_t offset; re_comp_flags flags; } regexp_split_ctx; static int num_res = 0; /* # of cached re's */ static cached_re_str re_array[MAX_CACHED_RES]; /* cached re's */ static regexp_matches_ctx *setup_regexp_matches(text *orig_str, text *pattern, text *flags); static ArrayType *perform_regexp_matches(regexp_matches_ctx *matchctx); static regexp_split_ctx *setup_regexp_split(text *str, text *pattern, text *flags); static Datum get_next_split(regexp_split_ctx *splitctx); /* * RE_compile_and_cache - compile a RE, caching if possible * * Returns regex_t * * * text_re --- the pattern, expressed as an *untoasted* TEXT object * cflags --- compile options for the pattern * * Pattern is given in the database encoding. We internally convert to * an array of pg_wchar, which is what Spencer's regex package wants. */ static regex_t * RE_compile_and_cache(text *text_re, int cflags) { int text_re_len = VARSIZE(text_re); pg_wchar *pattern; size_t pattern_len; int i; int regcomp_result; cached_re_str re_temp; char errMsg[100]; /* * Look for a match among previously compiled REs. Since the data * structure is self-organizing with most-used entries at the front, our * search strategy can just be to scan from the front. */ for (i = 0; i < num_res; i++) { if (VARSIZE(re_array[i].cre_pat) == text_re_len && memcmp(re_array[i].cre_pat, text_re, text_re_len) == 0 && re_array[i].cre_flags == cflags) { /* * Found a match; move it to front if not there already. */ if (i > 0) { re_temp = re_array[i]; memmove(&re_array[1], &re_array[0], i * sizeof(cached_re_str)); re_array[0] = re_temp; } return &re_array[0].cre_re; } } /* * Couldn't find it, so try to compile the new RE. To avoid leaking * resources on failure, we build into the re_temp local. */ /* Convert pattern string to wide characters */ pattern = (pg_wchar *) palloc((text_re_len - VARHDRSZ + 1) * sizeof(pg_wchar)); pattern_len = pg_mb2wchar_with_len(VARDATA(text_re), pattern, text_re_len - VARHDRSZ); regcomp_result = pg_regcomp(&re_temp.cre_re, pattern, pattern_len, cflags); pfree(pattern); if (regcomp_result != REG_OKAY) { /* re didn't compile */ pg_regerror(regcomp_result, &re_temp.cre_re, errMsg, sizeof(errMsg)); /* XXX should we pg_regfree here? */ ereport(ERROR, (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), errmsg("invalid regular expression: %s", errMsg))); } /* * use malloc/free for the cre_pat field because the storage has to * persist across transactions */ re_temp.cre_pat = malloc(text_re_len); if (re_temp.cre_pat == NULL) { pg_regfree(&re_temp.cre_re); ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); } memcpy(re_temp.cre_pat, text_re, text_re_len); re_temp.cre_flags = cflags; /* * Okay, we have a valid new item in re_temp; insert it into the storage * array. Discard last entry if needed. */ if (num_res >= MAX_CACHED_RES) { --num_res; Assert(num_res < MAX_CACHED_RES); pg_regfree(&re_array[num_res].cre_re); free(re_array[num_res].cre_pat); } if (num_res > 0) memmove(&re_array[1], &re_array[0], num_res * sizeof(cached_re_str)); re_array[0] = re_temp; num_res++; return &re_array[0].cre_re; } /* * RE_wchar_execute - execute a RE * * Returns TRUE on match, FALSE on no match * * re --- the compiled pattern as returned by RE_compile_and_cache * data --- the data to match against (need not be null-terminated) * data_len --- the length of the data string * start_search -- the offset in the data to start searching * nmatch, pmatch --- optional return area for match details * * Data is given as array of pg_wchar which is what Spencer's regex package * wants. */ static bool RE_wchar_execute(regex_t *re, pg_wchar *data, int data_len, size_t start_search, int nmatch, regmatch_t *pmatch) { int regexec_result; char errMsg[100]; /* Perform RE match and return result */ regexec_result = pg_regexec(re, data, data_len, start_search, NULL, /* no details */ nmatch, pmatch, 0); if (regexec_result != REG_OKAY && regexec_result != REG_NOMATCH) { /* re failed??? */ 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); } /* * RE_execute - execute a RE * * Returns TRUE on match, FALSE on no match * * re --- the compiled pattern as returned by RE_compile_and_cache * dat --- the data to match against (need not be null-terminated) * dat_len --- the length of the data string * nmatch, pmatch --- optional return area for match details * * Data is given in the database encoding. We internally * convert to array of pg_wchar which is what Spencer's regex package wants. */ static bool RE_execute(regex_t *re, char *dat, int dat_len, int nmatch, regmatch_t *pmatch) { pg_wchar *data; size_t data_len; bool match; /* Convert data string to wide characters */ data = (pg_wchar *) palloc((dat_len + 1) * sizeof(pg_wchar)); data_len = pg_mb2wchar_with_len(dat, data, dat_len); /* Perform RE match and return result */ match = RE_wchar_execute(re, data, data_len, 0, nmatch, pmatch); pfree(data); return match; } /* * RE_compile_and_execute - compile and execute a RE * * Returns TRUE on match, FALSE on no match * * text_re --- the pattern, expressed as an *untoasted* TEXT object * dat --- the data to match against (need not be null-terminated) * dat_len --- the length of the data string * cflags --- compile options for the pattern * nmatch, pmatch --- optional return area for match details * * Both pattern and data are given in the database encoding. We internally * convert to array of pg_wchar which is what Spencer's regex package wants. */ static bool RE_compile_and_execute(text *text_re, char *dat, int dat_len, int cflags, int nmatch, regmatch_t *pmatch) { regex_t *re; /* Compile RE */ re = RE_compile_and_cache(text_re, cflags); return RE_execute(re, dat, dat_len, nmatch, pmatch); } static void parse_re_comp_flags(re_comp_flags *flags, text *opts) { MemSet(flags, 0, sizeof(re_comp_flags)); flags->cflags = regex_flavor; if (opts) { char *opt_p = VARDATA(opts); size_t opt_len = VARSIZE(opts) - VARHDRSZ; int i; for (i = 0; i < opt_len; i++) { switch (opt_p[i]) { case 'g': flags->glob = true; break; case 'i': flags->cflags |= REG_ICASE; break; case 'm': case 'n': flags->cflags |= REG_NEWLINE; break; case 'p': flags->cflags |= REG_NLSTOP; flags->cflags &= ~REG_NLANCH; break; case 'w': flags->cflags &= ~REG_NLSTOP; flags->cflags |= REG_NLANCH; break; case 'x': flags->cflags |= REG_EXPANDED; break; default: ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid regexp option: %c", opt_p[i]))); break; } } } } /* * assign_regex_flavor - GUC hook to validate and set REGEX_FLAVOR */ const char * assign_regex_flavor(const char *value, bool doit, GucSource source) { if (pg_strcasecmp(value, "advanced") == 0) { if (doit) regex_flavor = REG_ADVANCED; } else if (pg_strcasecmp(value, "extended") == 0) { if (doit) regex_flavor = REG_EXTENDED; } else if (pg_strcasecmp(value, "basic") == 0) { if (doit) regex_flavor = REG_BASIC; } else return NULL; /* fail */ return value; /* OK */ } /* * interface routines called by the function manager */ Datum nameregexeq(PG_FUNCTION_ARGS) { Name n = PG_GETARG_NAME(0); text *p = PG_GETARG_TEXT_P(1); PG_RETURN_BOOL(RE_compile_and_execute(p, NameStr(*n), strlen(NameStr(*n)), regex_flavor, 0, NULL)); } Datum nameregexne(PG_FUNCTION_ARGS) { Name n = PG_GETARG_NAME(0); text *p = PG_GETARG_TEXT_P(1); PG_RETURN_BOOL(!RE_compile_and_execute(p, NameStr(*n), strlen(NameStr(*n)), regex_flavor, 0, NULL)); } Datum textregexeq(PG_FUNCTION_ARGS) { text *s = PG_GETARG_TEXT_P(0); text *p = PG_GETARG_TEXT_P(1); PG_RETURN_BOOL(RE_compile_and_execute(p, VARDATA(s), VARSIZE(s) - VARHDRSZ, regex_flavor, 0, NULL)); } Datum textregexne(PG_FUNCTION_ARGS) { text *s = PG_GETARG_TEXT_P(0); text *p = PG_GETARG_TEXT_P(1); PG_RETURN_BOOL(!RE_compile_and_execute(p, VARDATA(s), VARSIZE(s) - VARHDRSZ, regex_flavor, 0, NULL)); } /* * routines that use the regexp stuff, but ignore the case. * for this, we use the REG_ICASE flag to pg_regcomp */ Datum nameicregexeq(PG_FUNCTION_ARGS) { Name n = PG_GETARG_NAME(0); text *p = PG_GETARG_TEXT_P(1); PG_RETURN_BOOL(RE_compile_and_execute(p, NameStr(*n), strlen(NameStr(*n)), regex_flavor | REG_ICASE, 0, NULL)); } Datum nameicregexne(PG_FUNCTION_ARGS) { Name n = PG_GETARG_NAME(0); text *p = PG_GETARG_TEXT_P(1); PG_RETURN_BOOL(!RE_compile_and_execute(p, NameStr(*n), strlen(NameStr(*n)), regex_flavor | REG_ICASE, 0, NULL)); } Datum texticregexeq(PG_FUNCTION_ARGS) { text *s = PG_GETARG_TEXT_P(0); text *p = PG_GETARG_TEXT_P(1); PG_RETURN_BOOL(RE_compile_and_execute(p, VARDATA(s), VARSIZE(s) - VARHDRSZ, regex_flavor | REG_ICASE, 0, NULL)); } Datum texticregexne(PG_FUNCTION_ARGS) { text *s = PG_GETARG_TEXT_P(0); text *p = PG_GETARG_TEXT_P(1); PG_RETURN_BOOL(!RE_compile_and_execute(p, VARDATA(s), VARSIZE(s) - VARHDRSZ, regex_flavor | REG_ICASE, 0, NULL)); } /* * textregexsubstr() * Return a substring matched by a regular expression. */ Datum textregexsubstr(PG_FUNCTION_ARGS) { text *s = PG_GETARG_TEXT_P(0); text *p = PG_GETARG_TEXT_P(1); bool match; regmatch_t pmatch[2]; /* * We pass two regmatch_t structs to get info about the overall match and * the match for the first parenthesized subexpression (if any). If there * is a parenthesized subexpression, we return what it matched; else * return what the whole regexp matched. */ match = RE_compile_and_execute(p, VARDATA(s), VARSIZE(s) - VARHDRSZ, regex_flavor, 2, pmatch); /* match? then return the substring matching the pattern */ if (match) { int so, eo; so = pmatch[1].rm_so; eo = pmatch[1].rm_eo; if (so < 0 || eo < 0) { /* no parenthesized subexpression */ so = pmatch[0].rm_so; eo = pmatch[0].rm_eo; } return DirectFunctionCall3(text_substr, PointerGetDatum(s), Int32GetDatum(so + 1), Int32GetDatum(eo - so)); } PG_RETURN_NULL(); } /* * textregexreplace_noopt() * Return a string matched by a regular expression, with replacement. * * This version doesn't have an option argument: we default to case * sensitive match, replace the first instance only. */ Datum textregexreplace_noopt(PG_FUNCTION_ARGS) { text *s = PG_GETARG_TEXT_P(0); text *p = PG_GETARG_TEXT_P(1); text *r = PG_GETARG_TEXT_P(2); regex_t *re; re = RE_compile_and_cache(p, regex_flavor); PG_RETURN_TEXT_P(replace_text_regexp(s, (void *) re, r, false)); } /* * textregexreplace() * Return a string matched by a regular expression, with replacement. */ Datum textregexreplace(PG_FUNCTION_ARGS) { text *s = PG_GETARG_TEXT_P(0); text *p = PG_GETARG_TEXT_P(1); text *r = PG_GETARG_TEXT_P(2); text *opt = PG_GETARG_TEXT_P(3); regex_t *re; re_comp_flags flags; parse_re_comp_flags(&flags, opt); re = RE_compile_and_cache(p, flags.cflags); PG_RETURN_TEXT_P(replace_text_regexp(s, (void *) re, r, flags.glob)); } /* similar_escape() * Convert a SQL99 regexp pattern to POSIX style, so it can be used by * our regexp engine. */ Datum similar_escape(PG_FUNCTION_ARGS) { text *pat_text; text *esc_text; text *result; char *p, *e, *r; int plen, elen; bool afterescape = false; int nquotes = 0; /* This function is not strict, so must test explicitly */ if (PG_ARGISNULL(0)) PG_RETURN_NULL(); pat_text = PG_GETARG_TEXT_P(0); p = VARDATA(pat_text); plen = (VARSIZE(pat_text) - VARHDRSZ); if (PG_ARGISNULL(1)) { /* No ESCAPE clause provided; default to backslash as escape */ e = "\\"; elen = 1; } else { esc_text = PG_GETARG_TEXT_P(1); e = VARDATA(esc_text); elen = (VARSIZE(esc_text) - VARHDRSZ); if (elen == 0) e = NULL; /* no escape character */ else if (elen != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE), errmsg("invalid escape string"), errhint("Escape string must be empty or one character."))); } /*---------- * We surround the transformed input string with * ***:^(?: ... )$ * which is bizarre enough to require some explanation. "***:" is a * director prefix to force the regex to be treated as an ARE regardless * of the current regex_flavor setting. We need "^" and "$" to force * the pattern to match the entire input string as per SQL99 spec. The * "(?:" and ")" are a non-capturing set of parens; we have to have * parens in case the string contains "|", else the "^" and "$" will * be bound into the first and last alternatives which is not what we * want, and the parens must be non capturing because we don't want them * to count when selecting output for SUBSTRING. *---------- */ /* * We need room for the prefix/postfix plus as many as 2 output bytes per * input byte */ result = (text *) palloc(VARHDRSZ + 10 + 2 * plen); r = VARDATA(result); *r++ = '*'; *r++ = '*'; *r++ = '*'; *r++ = ':'; *r++ = '^'; *r++ = '('; *r++ = '?'; *r++ = ':'; while (plen > 0) { char pchar = *p; if (afterescape) { if (pchar == '"') /* for SUBSTRING patterns */ *r++ = ((nquotes++ % 2) == 0) ? '(' : ')'; else { *r++ = '\\'; *r++ = pchar; } afterescape = false; } else if (e && pchar == *e) { /* SQL99 escape character; do not send to output */ afterescape = true; } else if (pchar == '%') { *r++ = '.'; *r++ = '*'; } else if (pchar == '_') *r++ = '.'; else if (pchar == '\\' || pchar == '.' || pchar == '?' || pchar == '{') { *r++ = '\\'; *r++ = pchar; } else *r++ = pchar; p++, plen--; } *r++ = ')'; *r++ = '$'; SET_VARSIZE(result, r - ((char *) result)); PG_RETURN_TEXT_P(result); } #define PG_GETARG_TEXT_P_IF_EXISTS(_n) \ (PG_NARGS() > _n ? PG_GETARG_TEXT_P(_n) : NULL) Datum regexp_matches(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; MemoryContext oldcontext; regexp_matches_ctx *matchctx; if (SRF_IS_FIRSTCALL()) { text *pattern = PG_GETARG_TEXT_P(1); text *flags = PG_GETARG_TEXT_P_IF_EXISTS(2); funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* be sure to copy the input string into the multi-call ctx */ matchctx = setup_regexp_matches(PG_GETARG_TEXT_P_COPY(0), pattern, flags); MemoryContextSwitchTo(oldcontext); funcctx->user_fctx = (void *) matchctx; /* * Avoid run-away function by making sure we never iterate * more than the length of the text + 1 (the number of matches * an empty pattern will make is length + 1) */ if (matchctx->flags.glob) funcctx->max_calls = matchctx->wide_len + 1; else funcctx->max_calls = 0; } funcctx = SRF_PERCALL_SETUP(); matchctx = (regexp_matches_ctx *) funcctx->user_fctx; if (funcctx->call_cntr > funcctx->max_calls) { /* * If max_calls == 0, then we are doing a non-global match, we * should stop now, no problem. Otherwise, if we exceed * max_calls something really wonky is going on, since it is * returning more matches than there are characters in the * string, which should not happen */ if (funcctx->max_calls != 0) elog(ERROR, "set returning match function terminated after iterating %d times", funcctx->call_cntr); SRF_RETURN_DONE(funcctx); } if (matchctx->offset < matchctx->wide_len) { ArrayType *result_ary; if (matchctx->pmatch[0].rm_so == matchctx->pmatch[0].rm_eo) matchctx->offset++; result_ary = perform_regexp_matches(matchctx); if (result_ary != NULL) { matchctx->offset = matchctx->pmatch[0].rm_eo; SRF_RETURN_NEXT(funcctx, PointerGetDatum(result_ary)); } /* else fall through and return done */ } SRF_RETURN_DONE(funcctx); } Datum regexp_matches_no_flags(PG_FUNCTION_ARGS) { return regexp_matches(fcinfo); } static regexp_matches_ctx * setup_regexp_matches(text *orig_str, text *pattern, text *flags) { regexp_matches_ctx *matchctx = palloc(sizeof(regexp_matches_ctx)); matchctx->orig_str = orig_str; matchctx->orig_len = VARSIZE(matchctx->orig_str) - VARHDRSZ; parse_re_comp_flags(&matchctx->flags, flags); matchctx->cpattern = RE_compile_and_cache(pattern, matchctx->flags.cflags); matchctx->pmatch = palloc(sizeof(regmatch_t) * (matchctx->cpattern->re_nsub + 1)); matchctx->offset = 0; matchctx->wide_str = palloc(sizeof(pg_wchar) * (matchctx->orig_len + 1)); matchctx->wide_len = pg_mb2wchar_with_len(VARDATA(matchctx->orig_str), matchctx->wide_str, matchctx->orig_len); matchctx->pmatch[0].rm_so = -1; /* both < 0 but not equal */ matchctx->pmatch[0].rm_eo = -2; return matchctx; } static ArrayType * perform_regexp_matches(regexp_matches_ctx *matchctx) { Datum *elems; bool *nulls; Datum fullmatch; /* used to avoid a palloc if no matches */ int ndims = 1; int dims[1]; int lbs[1] = {1}; if (RE_wchar_execute(matchctx->cpattern, matchctx->wide_str, matchctx->wide_len, matchctx->offset, matchctx->cpattern->re_nsub + 1, matchctx->pmatch) == false) return NULL; if (matchctx->cpattern->re_nsub > 0) { int i; elems = palloc(sizeof(Datum) * matchctx->cpattern->re_nsub); nulls = palloc(sizeof(bool) * matchctx->cpattern->re_nsub); dims[0] = matchctx->cpattern->re_nsub; for (i = 0; i < matchctx->cpattern->re_nsub; i++) { int so = matchctx->pmatch[i + 1].rm_so; int eo = matchctx->pmatch[i + 1].rm_eo; if (so < 0 || eo < 0) { elems[i] = 0; nulls[i] = true; } else { elems[i] = DirectFunctionCall3(text_substr, PointerGetDatum(matchctx->orig_str), Int32GetDatum(so + 1), Int32GetDatum(eo - so)); nulls[i] = false; } } } else { int so = matchctx->pmatch[0].rm_so; int eo = matchctx->pmatch[0].rm_eo; if (so < 0 || eo < 0) elog(ERROR, "regexp code said it had a match, but did not return it"); fullmatch = DirectFunctionCall3(text_substr, PointerGetDatum(matchctx->orig_str), Int32GetDatum(so + 1), Int32GetDatum(eo - so)); elems = &fullmatch; nulls = NULL; dims[0] = 1; } /* XXX: this hardcodes assumptions about the text type */ return construct_md_array(elems, nulls, ndims, dims, lbs, TEXTOID, -1, false, 'i'); } Datum regexp_split_to_table(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; regexp_split_ctx *splitctx; if (SRF_IS_FIRSTCALL()) { text *pattern = PG_GETARG_TEXT_P(1); text *flags = PG_GETARG_TEXT_P_IF_EXISTS(2); MemoryContext oldcontext; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); splitctx = setup_regexp_split(PG_GETARG_TEXT_P_COPY(0), pattern, flags); MemoryContextSwitchTo(oldcontext); funcctx->user_fctx = (void *) splitctx; /* * Avoid run-away function by making sure we never iterate * more than the length of the text */ funcctx->max_calls = splitctx->wide_len; } funcctx = SRF_PERCALL_SETUP(); splitctx = (regexp_split_ctx *) funcctx->user_fctx; if (funcctx->call_cntr > funcctx->max_calls) { /* * If we exceed wide_len something really wonky is going on, * since it is returning more matches than there are * characters in the string, which should not happen */ elog(ERROR, "set returning split function terminated after iterating %d times", funcctx->call_cntr); } if (splitctx->offset < splitctx->wide_len) SRF_RETURN_NEXT(funcctx, get_next_split(splitctx)); else SRF_RETURN_DONE(funcctx); } Datum regexp_split_to_table_no_flags(PG_FUNCTION_ARGS) { return regexp_split_to_table(fcinfo); } Datum regexp_split_to_array(PG_FUNCTION_ARGS) { ArrayBuildState *astate = NULL; regexp_split_ctx *splitctx; int nitems; splitctx = setup_regexp_split(PG_GETARG_TEXT_P(0), PG_GETARG_TEXT_P(1), PG_GETARG_TEXT_P_IF_EXISTS(2)); for (nitems = 0; splitctx->offset < splitctx->wide_len; nitems++) { if (nitems > splitctx->wide_len) elog(ERROR, "split function terminated after iterating %d times", nitems); astate = accumArrayResult(astate, get_next_split(splitctx), false, TEXTOID, CurrentMemoryContext); } PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext)); } Datum regexp_split_to_array_no_flags(PG_FUNCTION_ARGS) { return regexp_split_to_array(fcinfo); } static regexp_split_ctx * setup_regexp_split(text *str, text *pattern, text *flags) { regexp_split_ctx *splitctx = palloc(sizeof(regexp_split_ctx)); splitctx->orig_str = str; splitctx->orig_len = VARSIZE(splitctx->orig_str) - VARHDRSZ; parse_re_comp_flags(&splitctx->flags, flags); if (splitctx->flags.glob) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("regexp_split does not support the global option"))); splitctx->cpattern = RE_compile_and_cache(pattern, splitctx->flags.cflags); splitctx->wide_str = palloc(sizeof(pg_wchar) * (splitctx->orig_len + 1)); splitctx->wide_len = pg_mb2wchar_with_len(VARDATA(splitctx->orig_str), splitctx->wide_str, splitctx->orig_len); splitctx->offset = 0; splitctx->match.rm_so = -1; /* both < 0 but not equal */ splitctx->match.rm_eo = -2; return splitctx; } static Datum get_next_split(regexp_split_ctx *splitctx) { regmatch_t *pmatch = &(splitctx->match); for (;;) { Datum result; int startpos = splitctx->offset + 1; /* * If the last match was zero-length, we need to push the * offset forward to avoid matching the same place forever */ if (pmatch->rm_so == pmatch->rm_eo) splitctx->offset++; if (RE_wchar_execute(splitctx->cpattern, splitctx->wide_str, splitctx->wide_len, splitctx->offset, 1, pmatch)) { int length = splitctx->match.rm_so - startpos + 1; /* * If we are trying to match at the beginning of the string and * we got a zero-length match, or if we just matched where we * left off last time, go around the loop again and increment * the offset. If we have incremented the offset already and * it matched at the new offset, that's ok */ if (length == 0) continue; result = DirectFunctionCall3(text_substr, PointerGetDatum(splitctx->orig_str), Int32GetDatum(startpos), Int32GetDatum(length)); /* set the offset to the end of this match for next time */ splitctx->offset = pmatch->rm_eo; return result; } /* no more matches, return rest of string */ result = DirectFunctionCall2(text_substr_no_len, PointerGetDatum(splitctx->orig_str), Int32GetDatum(startpos)); /* so we know we're done next time through */ splitctx->offset = splitctx->wide_len; return result; } } /* * report whether regex_flavor is currently BASIC */ bool regex_flavor_is_basic(void) { return (regex_flavor == REG_BASIC); }