/*-------------------------------------------------------------------------
 *
 * 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.70 2007/03/20 05:44:59 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 "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;

	/* text type info */
	Oid			  param_type;
	int16		  typlen;
	bool		  typbyval;
	char		  typalign;
} 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(FunctionCallInfo fcinfo,
												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(fcinfo, 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(FunctionCallInfo fcinfo, 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;

	/* get text type oid, too lazy to do it some other way */
	matchctx->param_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
	get_typlenbyvalalign(matchctx->param_type, &matchctx->typlen,
						 &matchctx->typbyval, &matchctx->typalign);

	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;
	}

	return construct_md_array(elems, nulls, ndims, dims, lbs,
							  matchctx->param_type, matchctx->typlen,
							  matchctx->typbyval, matchctx->typalign);
}

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;
	Oid 				 param_type;
	int 				 nitems;

	splitctx = setup_regexp_split(PG_GETARG_TEXT_P(0),
								  PG_GETARG_TEXT_P(1),
								  PG_GETARG_TEXT_P_IF_EXISTS(2));

	/* get text type oid, too lazy to do it some other way */
	param_type = get_fn_expr_argtype(fcinfo->flinfo, 0);

	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,
								  param_type,
								  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);
}
