text: /*-------------------------------------------------------------------------
   * oracle_compat.c
   *	Oracle compatible functions.
   *
   * Copyright (c) 1996-2009, PostgreSQL Global Development Group
   *
   *	Author: Edmund Mergl <E.Mergl@bawue.de>
   *	Multibyte enhancement: Tatsuo Ishii <ishii@postgresql.org>
   *
   *
   * IDENTIFICATION
   *	$PostgreSQL: pgsql/src/backend/utils/adt/oracle_compat.c,v 1.84 2009/06/11 14:49:03 momjian Exp $
   *
   *-------------------------------------------------------------------------
   */
  #include "postgres.h"
  
  #include "utils/builtins.h"
  #include "utils/formatting.h"
  #include "mb/pg_wchar.h"
  
  
  static text *dotrim(const char *string, int stringlen,
  	   const char *set, int setlen,
  	   bool doltrim, bool dortrim);
  
  
  /********************************************************************
   *
   * lower
   *
   * Syntax:
   *
   *	 text lower(text string)
   *
   * Purpose:
   *
   *	 Returns string, with all letters forced to lowercase.
   *
   ********************************************************************/
  
  Datum
  lower(PG_FUNCTION_ARGS)
  {
  	text	   *in_string = PG_GETARG_TEXT_PP(0);
  	char	   *out_string;
  	text	   *result;
  
  	out_string = str_tolower(VARDATA_ANY(in_string),
  							 VARSIZE_ANY_EXHDR(in_string));
  	result = cstring_to_text(out_string);
  	pfree(out_string);
  
  	PG_RETURN_TEXT_P(result);
  }
  
  
  /********************************************************************
   *
   * upper
   *
   * Syntax:
   *
   *	 text upper(text string)
   *
   * Purpose:
   *
   *	 Returns string, with all letters forced to uppercase.
   *
   ********************************************************************/
  
  Datum
  upper(PG_FUNCTION_ARGS)
  {
  	text	   *in_string = PG_GETARG_TEXT_PP(0);
  	char	   *out_string;
  	text	   *result;
  
  	out_string = str_toupper(VARDATA_ANY(in_string),
  							 VARSIZE_ANY_EXHDR(in_string));
  	result = cstring_to_text(out_string);
  	pfree(out_string);
  
  	PG_RETURN_TEXT_P(result);
  }
  
  
  /********************************************************************
   *
   * initcap
   *
   * Syntax:
   *
   *	 text initcap(text string)
   *
   * Purpose:
   *
   *	 Returns string, with first letter of each word in uppercase, all
   *	 other letters in lowercase. A word is defined as a sequence of
   *	 alphanumeric characters, delimited by non-alphanumeric
   *	 characters.
   *
   ********************************************************************/
  
  Datum
  initcap(PG_FUNCTION_ARGS)
  {
  	text	   *in_string = PG_GETARG_TEXT_PP(0);
  	char	   *out_string;
  	text	   *result;
  
  	out_string = str_initcap(VARDATA_ANY(in_string),
  							 VARSIZE_ANY_EXHDR(in_string));
  	result = cstring_to_text(out_string);
  	pfree(out_string);
  
  	PG_RETURN_TEXT_P(result);
  }
  
  
  /********************************************************************
   *
   * lpad
   *
   * Syntax:
   *
   *	 text lpad(text string1, int4 len, text string2)
   *
   * Purpose:
   *
   *	 Returns string1, left-padded to length len with the sequence of
   *	 characters in string2.  If len is less than the length of string1,
   *	 instead truncate (on the right) to len.
   *
   ********************************************************************/
  
  Datum
  lpad(PG_FUNCTION_ARGS)
  {
  	text	   *string1 = PG_GETARG_TEXT_PP(0);
  	int32		len = PG_GETARG_INT32(1);
  	text	   *string2 = PG_GETARG_TEXT_PP(2);
  	text	   *ret;
  	char	   *ptr1,
  			   *ptr2,
  			   *ptr2start,
  			   *ptr2end,
  			   *ptr_ret;
  	int			m,
  				s1len,
  				s2len;
  
  	int			bytelen;
  
  	/* Negative len is silently taken as zero */
  	if (len < 0)
  		len = 0;
  
  	s1len = VARSIZE_ANY_EXHDR(string1);
  	if (s1len < 0)
  		s1len = 0;				/* shouldn't happen */
  
  	s2len = VARSIZE_ANY_EXHDR(string2);
  	if (s2len < 0)
  		s2len = 0;				/* shouldn't happen */
  
  	s1len = pg_mbstrlen_with_len(VARDATA_ANY(string1), s1len);
  
  	if (s1len > len)
  		s1len = len;			/* truncate string1 to len chars */
  
  	if (s2len <= 0)
  		len = s1len;			/* nothing to pad with, so don't pad */
  
  	bytelen = pg_database_encoding_max_length() * len;
  
  	/* check for integer overflow */
  	if (len != 0 && bytelen / pg_database_encoding_max_length() != len)
  		ereport(ERROR,
  				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
  				 errmsg("requested length too large")));
  
  	ret = (text *) palloc(VARHDRSZ + bytelen);
  
  	m = len - s1len;
  
  	ptr2 = ptr2start = VARDATA_ANY(string2);
  	ptr2end = ptr2 + s2len;
  	ptr_ret = VARDATA(ret);
  
  	while (m--)
  	{
  		int			mlen = pg_mblen(ptr2);
  
  		memcpy(ptr_ret, ptr2, mlen);
  		ptr_ret += mlen;
  		ptr2 += mlen;
  		if (ptr2 == ptr2end)	/* wrap around at end of s2 */
  			ptr2 = ptr2start;
  	}
  
  	ptr1 = VARDATA_ANY(string1);
  
  	while (s1len--)
  	{
  		int			mlen = pg_mblen(ptr1);
  
  		memcpy(ptr_ret, ptr1, mlen);
  		ptr_ret += mlen;
  		ptr1 += mlen;
  	}
  
  	SET_VARSIZE(ret, ptr_ret - (char *) ret);
  
  	PG_RETURN_TEXT_P(ret);
  }
  
  
  /********************************************************************
   *
   * rpad
   *
   * Syntax:
   *
   *	 text rpad(text string1, int4 len, text string2)
   *
   * Purpose:
   *
   *	 Returns string1, right-padded to length len with the sequence of
   *	 characters in string2.  If len is less than the length of string1,
   *	 instead truncate (on the right) to len.
   *
   ********************************************************************/
  
  Datum
  rpad(PG_FUNCTION_ARGS)
  {
  	text	   *string1 = PG_GETARG_TEXT_PP(0);
  	int32		len = PG_GETARG_INT32(1);
  	text	   *string2 = PG_GETARG_TEXT_PP(2);
  	text	   *ret;
  	char	   *ptr1,
  			   *ptr2,
  			   *ptr2start,
  			   *ptr2end,
  			   *ptr_ret;
  	int			m,
  				s1len,
  				s2len;
  
  	int			bytelen;
  
  	/* Negative len is silently taken as zero */
  	if (len < 0)
  		len = 0;
  
  	s1len = VARSIZE_ANY_EXHDR(string1);
  	if (s1len < 0)
  		s1len = 0;				/* shouldn't happen */
  
  	s2len = VARSIZE_ANY_EXHDR(string2);
  	if (s2len < 0)
  		s2len = 0;				/* shouldn't happen */
  
  	s1len = pg_mbstrlen_with_len(VARDATA_ANY(string1), s1len);
  
  	if (s1len > len)
  		s1len = len;			/* truncate string1 to len chars */
  
  	if (s2len <= 0)
  		len = s1len;			/* nothing to pad with, so don't pad */
  
  	bytelen = pg_database_encoding_max_length() * len;
  
  	/* Check for integer overflow */
  	if (len != 0 && bytelen / pg_database_encoding_max_length() != len)
  		ereport(ERROR,
  				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
  				 errmsg("requested length too large")));
  
  	ret = (text *) palloc(VARHDRSZ + bytelen);
  	m = len - s1len;
  
  	ptr1 = VARDATA_ANY(string1);
  	ptr_ret = VARDATA(ret);
  
  	while (s1len--)
  	{
  		int			mlen = pg_mblen(ptr1);
  
  		memcpy(ptr_ret, ptr1, mlen);
  		ptr_ret += mlen;
  		ptr1 += mlen;
  	}
  
  	ptr2 = ptr2start = VARDATA_ANY(string2);
  	ptr2end = ptr2 + s2len;
  
  	while (m--)
  	{
  		int			mlen = pg_mblen(ptr2);
  
  		memcpy(ptr_ret, ptr2, mlen);
  		ptr_ret += mlen;
  		ptr2 += mlen;
  		if (ptr2 == ptr2end)	/* wrap around at end of s2 */
  			ptr2 = ptr2start;
  	}
  
  	SET_VARSIZE(ret, ptr_ret - (char *) ret);
  
  	PG_RETURN_TEXT_P(ret);
  }
  
  
  /********************************************************************
   *
   * btrim
   *
   * Syntax:
   *
   *	 text btrim(text string, text set)
   *
   * Purpose:
   *
   *	 Returns string with characters removed from the front and back
   *	 up to the first character not in set.
   *
   ********************************************************************/
  
  Datum
  btrim(PG_FUNCTION_ARGS)
  {
  	text	   *string = PG_GETARG_TEXT_PP(0);
  	text	   *set = PG_GETARG_TEXT_PP(1);
  	text	   *ret;
  
  	ret = dotrim(VARDATA_ANY(string), VARSIZE_ANY_EXHDR(string),
  				 VARDATA_ANY(set), VARSIZE_ANY_EXHDR(set),
  				 true, true);
  
  	PG_RETURN_TEXT_P(ret);
  }
  
  /********************************************************************
   *
   * btrim1 --- btrim with set fixed as ' '
   *
   ********************************************************************/
  
  Datum
  btrim1(PG_FUNCTION_ARGS)
  {
  	text	   *string = PG_GETARG_TEXT_PP(0);
  	text	   *ret;
  
  	ret = dotrim(VARDATA_ANY(string), VARSIZE_ANY_EXHDR(string),
  				 " ", 1,
  				 true, true);
  
  	PG_RETURN_TEXT_P(ret);
  }
  
  /*
   * Common implementation for btrim, ltrim, rtrim
   */
  static text *
  dotrim(const char *string, int stringlen,
  	   const char *set, int setlen,
  	   bool doltrim, bool dortrim)
  {
  	int			i;
  
  	/* Nothing to do if either string or set is empty */
  	if (stringlen > 0 && setlen > 0)
  	{
  		if (pg_database_encoding_max_length() > 1)
  		{
  			/*
  			 * In the multibyte-encoding case, build arrays of pointers to
  			 * character starts, so that we can avoid inefficient checks in
  			 * the inner loops.
  			 */
  			const char **stringchars;
  			const char **setchars;
  			int		   *stringmblen;
  			int		   *setmblen;
  			int			stringnchars;
  			int			setnchars;
  			int			resultndx;
  			int			resultnchars;
  			const char *p;
  			int			len;
  			int			mblen;
  			const char *str_pos;
  			int			str_len;
  
  			stringchars = (const char **) palloc(stringlen * sizeof(char *));
  			stringmblen = (int *) palloc(stringlen * sizeof(int));
  			stringnchars = 0;
  			p = string;
  			len = stringlen;
  			while (len > 0)
  			{
  				stringchars[stringnchars] = p;
  				stringmblen[stringnchars] = mblen = pg_mblen(p);
  				stringnchars++;
  				p += mblen;
  				len -= mblen;
  			}
  
  			setchars = (const char **) palloc(setlen * sizeof(char *));
  			setmblen = (int *) palloc(setlen * sizeof(int));
  			setnchars = 0;
  			p = set;
  			len = setlen;
  			while (len > 0)
  			{
  				setchars[setnchars] = p;
  				setmblen[setnchars] = mblen = pg_mblen(p);
  				setnchars++;
  				p += mblen;
  				len -= mblen;
  			}
  
  			resultndx = 0;		/* index in stringchars[] */
  			resultnchars = stringnchars;
  
  			if (doltrim)
  			{
  				while (resultnchars > 0)
  				{
  					str_pos = stringchars[resultndx];
  					str_len = stringmblen[resultndx];
  					for (i = 0; i < setnchars; i++)
  					{
  						if (str_len == setmblen[i] &&
  							memcmp(str_pos, setchars[i], str_len) == 0)
  							break;
  					}
  					if (i >= setnchars)
  						break;	/* no match here */
  					string += str_len;
  					stringlen -= str_len;
  					resultndx++;
  					resultnchars--;
  				}
  			}
  
  			if (dortrim)
  			{
  				while (resultnchars > 0)
  				{
  					str_pos = stringchars[resultndx + resultnchars - 1];
  					str_len = stringmblen[resultndx + resultnchars - 1];
  					for (i = 0; i < setnchars; i++)
  					{
  						if (str_len == setmblen[i] &&
  							memcmp(str_pos, setchars[i], str_len) == 0)
  							break;
  					}
  					if (i >= setnchars)
  						break;	/* no match here */
  					stringlen -= str_len;
  					resultnchars--;
  				}
  			}
  
  			pfree(stringchars);
  			pfree(stringmblen);
  			pfree(setchars);
  			pfree(setmblen);
  		}
  		else
  		{
  			/*
  			 * In the single-byte-encoding case, we don't need such overhead.
  			 */
  			if (doltrim)
  			{
  				while (stringlen > 0)
  				{
  					char		str_ch = *string;
  
  					for (i = 0; i < setlen; i++)
  					{
  						if (str_ch == set[i])
  							break;
  					}
  					if (i >= setlen)
  						break;	/* no match here */
  					string++;
  					stringlen--;
  				}
  			}
  
  			if (dortrim)
  			{
  				while (stringlen > 0)
  				{
  					char		str_ch = string[stringlen - 1];
  
  					for (i = 0; i < setlen; i++)
  					{
  						if (str_ch == set[i])
  							break;
  					}
  					if (i >= setlen)
  						break;	/* no match here */
  					stringlen--;
  				}
  			}
  		}
  	}
  
  	/* Return selected portion of string */
  	return cstring_to_text_with_len(string, stringlen);
  }
  
  /********************************************************************
   *
   * byteatrim
   *
   * Syntax:
   *
   *	 bytea byteatrim(byta string, bytea set)
   *
   * Purpose:
   *
   *	 Returns string with characters removed from the front and back
   *	 up to the first character not in set.
   *
   * Cloned from btrim and modified as required.
   ********************************************************************/
  
  Datum
  byteatrim(PG_FUNCTION_ARGS)
  {
  	bytea	   *string = PG_GETARG_BYTEA_PP(0);
  	bytea	   *set = PG_GETARG_BYTEA_PP(1);
  	bytea	   *ret;
  	char	   *ptr,
  			   *end,
  			   *ptr2,
  			   *ptr2start,
  			   *end2;
  	int			m,
  				stringlen,
  				setlen;
  
  	stringlen = VARSIZE_ANY_EXHDR(string);
  	setlen = VARSIZE_ANY_EXHDR(set);
  
  	if (stringlen <= 0 || setlen <= 0)
  		PG_RETURN_BYTEA_P(string);
  
  	m = stringlen;
  	ptr = VARDATA_ANY(string);
  	end = ptr + stringlen - 1;
  	ptr2start = VARDATA_ANY(set);
  	end2 = ptr2start + setlen - 1;
  
  	while (m > 0)
  	{
  		ptr2 = ptr2start;
  		while (ptr2 <= end2)
  		{
  			if (*ptr == *ptr2)
  				break;
  			++ptr2;
  		}
  		if (ptr2 > end2)
  			break;
  		ptr++;
  		m--;
  	}
  
  	while (m > 0)
  	{
  		ptr2 = ptr2start;
  		while (ptr2 <= end2)
  		{
  			if (*end == *ptr2)
  				break;
  			++ptr2;
  		}
  		if (ptr2 > end2)
  			break;
  		end--;
  		m--;
  	}
  
  	ret = (bytea *) palloc(VARHDRSZ + m);
  	SET_VARSIZE(ret, VARHDRSZ + m);
  	memcpy(VARDATA(ret), ptr, m);
  
  	PG_RETURN_BYTEA_P(ret);
  }
  
  /********************************************************************
   *
   * ltrim
   *
   * Syntax:
   *
   *	 text ltrim(text string, text set)
   *
   * Purpose:
   *
   *	 Returns string with initial characters removed up to the first
   *	 character not in set.
   *
   ********************************************************************/
  
  Datum
  ltrim(PG_FUNCTION_ARGS)
  {
  	text	   *string = PG_GETARG_TEXT_PP(0);
  	text	   *set = PG_GETARG_TEXT_PP(1);
  	text	   *ret;
  
  	ret = dotrim(VARDATA_ANY(string), VARSIZE_ANY_EXHDR(string),
  				 VARDATA_ANY(set), VARSIZE_ANY_EXHDR(set),
  				 true, false);
  
  	PG_RETURN_TEXT_P(ret);
  }
  
  /********************************************************************
   *
   * ltrim1 --- ltrim with set fixed as ' '
   *
   ********************************************************************/
  
  Datum
  ltrim1(PG_FUNCTION_ARGS)
  {
  	text	   *string = PG_GETARG_TEXT_PP(0);
  	text	   *ret;
  
  	ret = dotrim(VARDATA_ANY(string), VARSIZE_ANY_EXHDR(string),
  				 " ", 1,
  				 true, false);
  
  	PG_RETURN_TEXT_P(ret);
  }
  
  /********************************************************************
   *
   * rtrim
   *
   * Syntax:
   *
   *	 text rtrim(text string, text set)
   *
   * Purpose:
   *
   *	 Returns string with final characters removed after the last
   *	 character not in set.
   *
   ********************************************************************/
  
  Datum
  rtrim(PG_FUNCTION_ARGS)
  {
  	text	   *string = PG_GETARG_TEXT_PP(0);
  	text	   *set = PG_GETARG_TEXT_PP(1);
  	text	   *ret;
  
  	ret = dotrim(VARDATA_ANY(string), VARSIZE_ANY_EXHDR(string),
  				 VARDATA_ANY(set), VARSIZE_ANY_EXHDR(set),
  				 false, true);
  
  	PG_RETURN_TEXT_P(ret);
  }
  
  /********************************************************************
   *
   * rtrim1 --- rtrim with set fixed as ' '
   *
   ********************************************************************/
  
  Datum
  rtrim1(PG_FUNCTION_ARGS)
  {
  	text	   *string = PG_GETARG_TEXT_PP(0);
  	text	   *ret;
  
  	ret = dotrim(VARDATA_ANY(string), VARSIZE_ANY_EXHDR(string),
  				 " ", 1,
  				 false, true);
  
  	PG_RETURN_TEXT_P(ret);
  }
  
  
  /********************************************************************
   *
   * translate
   *
   * Syntax:
   *
   *	 text translate(text string, text from, text to)
   *
   * Purpose:
   *
   *	 Returns string after replacing all occurrences of characters in from
   *	 with the corresponding character in to.  If from is longer than to,
   *	 occurrences of the extra characters in from are deleted.
   *	 Improved by Edwin Ramirez <ramirez@doc.mssm.edu>.
   *
   ********************************************************************/
  
  Datum
  translate(PG_FUNCTION_ARGS)
  {
  	text	   *string = PG_GETARG_TEXT_PP(0);
  	text	   *from = PG_GETARG_TEXT_PP(1);
  	text	   *to = PG_GETARG_TEXT_PP(2);
  	text	   *result;
  	char	   *from_ptr,
  			   *to_ptr;
  	char	   *source,
  			   *target;
  	int			m,
  				fromlen,
  				tolen,
  				retlen,
  				i;
  	int			worst_len;
  	int			len;
  	int			source_len;
  	int			from_index;
  
  	m = VARSIZE_ANY_EXHDR(string);
  	if (m <= 0)
  		PG_RETURN_TEXT_P(string);
  	source = VARDATA_ANY(string);
  
  	fromlen = VARSIZE_ANY_EXHDR(from);
  	from_ptr = VARDATA_ANY(from);
  	tolen = VARSIZE_ANY_EXHDR(to);
  	to_ptr = VARDATA_ANY(to);
  
  	/*
  	 * The worst-case expansion is to substitute a max-length character for a
  	 * single-byte character at each position of the string.
  	 */
  	worst_len = pg_database_encoding_max_length() * m;
  
  	/* check for integer overflow */
  	if (worst_len / pg_database_encoding_max_length() != m)
  		ereport(ERROR,
  				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
  				 errmsg("requested length too large")));
  
  	result = (text *) palloc(worst_len + VARHDRSZ);
  	target = VARDATA(result);
  	retlen = 0;
  
  	while (m > 0)
  	{
  		source_len = pg_mblen(source);
  		from_index = 0;
  
  		for (i = 0; i < fromlen; i += len)
  		{
  			len = pg_mblen(&from_ptr[i]);
  			if (len == source_len &&
  				memcmp(source, &from_ptr[i], len) == 0)
  				break;
  
  			from_index++;
  		}
  		if (i < fromlen)
  		{
  			/* substitute */
  			char	   *p = to_ptr;
  
  			for (i = 0; i < from_index; i++)
  			{
  				p += pg_mblen(p);
  				if (p >= (to_ptr + tolen))
  					break;
  			}
  			if (p < (to_ptr + tolen))
  			{
  				len = pg_mblen(p);
  				memcpy(target, p, len);
  				target += len;
  				retlen += len;
  			}
  
  		}
  		else
  		{
  			/* no match, so copy */
  			memcpy(target, source, source_len);
  			target += source_len;
  			retlen += source_len;
  		}
  
  		source += source_len;
  		m -= source_len;
  	}
  
  	SET_VARSIZE(result, retlen + VARHDRSZ);
  
  	/*
  	 * The function result is probably much bigger than needed, if we're using
  	 * a multibyte encoding, but it's not worth reallocating it; the result
  	 * probably won't live long anyway.
  	 */
  
  	PG_RETURN_TEXT_P(result);
  }
  
  /********************************************************************
   *
   * ascii
   *
   * Syntax:
   *
   *	 int ascii(text string)
   *
   * Purpose:
   *
   *	 Returns the decimal representation of the first character from
   *	 string.
   *	 If the string is empty we return 0.
   *	 If the database encoding is UTF8, we return the Unicode codepoint.
   *	 If the database encoding is any other multi-byte encoding, we
   *	 return the value of the first byte if it is an ASCII character
   *	 (range 1 .. 127), or raise an error.
   *	 For all other encodings we return the value of the first byte,
   *	 (range 1..255).
   *
   ********************************************************************/
  
  Datum
  ascii(PG_FUNCTION_ARGS)
  {
  	text	   *string = PG_GETARG_TEXT_PP(0);
  	int			encoding = GetDatabaseEncoding();
  	unsigned char *data;
  
  	if (VARSIZE_ANY_EXHDR(string) <= 0)
  		PG_RETURN_INT32(0);
  
  	data = (unsigned char *) VARDATA_ANY(string);
  
  	if (encoding == PG_UTF8 && *data > 127)
  	{
  		/* return the code point for Unicode */
  
  		int			result = 0,
  					tbytes = 0,
  					i;
  
  		if (*data >= 0xF0)
  		{
  			result = *data & 0x07;
  			tbytes = 3;
  		}
  		else if (*data >= 0xE0)
  		{
  			result = *data & 0x0F;
  			tbytes = 2;
  		}
  		else
  		{
  			Assert(*data > 0xC0);
  			result = *data & 0x1f;
  			tbytes = 1;
  		}
  
  		Assert(tbytes > 0);
  
  		for (i = 1; i <= tbytes; i++)
  		{
  			Assert((data[i] & 0xC0) == 0x80);
  			result = (result << 6) + (data[i] & 0x3f);
  		}
  
  		PG_RETURN_INT32(result);
  	}
  	else
  	{
  		if (pg_encoding_max_length(encoding) > 1 && *data > 127)
  			ereport(ERROR,
  					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
  					 errmsg("requested character too large")));
  
  
  		PG_RETURN_INT32((int32) *data);
  	}
  }
  
  /********************************************************************
   *
   * chr
   *
   * Syntax:
   *
   *	 text chr(int val)
   *
   * Purpose:
   *
   *	Returns the character having the binary equivalent to val.
   *
   * For UTF8 we treat the argumwent as a Unicode code point.
   * For other multi-byte encodings we raise an error for arguments
   * outside the strict ASCII range (1..127).
   *
   * It's important that we don't ever return a value that is not valid
   * in the database encoding, so that this doesn't become a way for
   * invalid data to enter the database.
   *
   ********************************************************************/
  
  Datum
  chr			(PG_FUNCTION_ARGS)
  {
  	uint32		cvalue = PG_GETARG_UINT32(0);
  	text	   *result;
  	int			encoding = GetDatabaseEncoding();
  
  	if (encoding == PG_UTF8 && cvalue > 127)
  	{
  		/* for Unicode we treat the argument as a code point */
  		int			bytes;
  		char	   *wch;
  
  		/* We only allow valid Unicode code points */
  		if (cvalue > 0x001fffff)
  			ereport(ERROR,
  					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
  					 errmsg("requested character too large for encoding: %d",
  							cvalue)));
  
  		if (cvalue > 0xffff)
  			bytes = 4;
  		else if (cvalue > 0x07ff)
  			bytes = 3;
  		else
  			bytes = 2;
  
  		result = (text *) palloc(VARHDRSZ + bytes);
  		SET_VARSIZE(result, VARHDRSZ + bytes);
  		wch = VARDATA(result);
  
  		if (bytes == 2)
  		{
  			wch[0] = 0xC0 | ((cvalue >> 6) & 0x1F);
  			wch[1] = 0x80 | (cvalue & 0x3F);
  		}
  		else if (bytes == 3)
  		{
  			wch[0] = 0xE0 | ((cvalue >> 12) & 0x0F);
  			wch[1] = 0x80 | ((cvalue >> 6) & 0x3F);
  			wch[2] = 0x80 | (cvalue & 0x3F);
  		}
  		else
  		{
  			wch[0] = 0xF0 | ((cvalue >> 18) & 0x07);
  			wch[1] = 0x80 | ((cvalue >> 12) & 0x3F);
  			wch[2] = 0x80 | ((cvalue >> 6) & 0x3F);
  			wch[3] = 0x80 | (cvalue & 0x3F);
  		}
  
  	}
  
  	else
  	{
  		bool		is_mb;
  
  		/*
  		 * Error out on arguments that make no sense or that we can't validly
  		 * represent in the encoding.
  		 */
  
  		if (cvalue == 0)
  			ereport(ERROR,
  					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
  					 errmsg("null character not permitted")));
  
  		is_mb = pg_encoding_max_length(encoding) > 1;
  
  		if ((is_mb && (cvalue > 127)) || (!is_mb && (cvalue > 255)))
  			ereport(ERROR,
  					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
  					 errmsg("requested character too large for encoding: %d",
  							cvalue)));
  
  
  		result = (text *) palloc(VARHDRSZ + 1);
  		SET_VARSIZE(result, VARHDRSZ + 1);
  		*VARDATA(result) = (char) cvalue;
  	}
  
  	PG_RETURN_TEXT_P(result);
  }
  
  /********************************************************************
   *
   * repeat
   *
   * Syntax:
   *
   *	 text repeat(text string, int val)
   *
   * Purpose:
   *
   *	Repeat string by val.
   *
   ********************************************************************/
  
  Datum
  repeat(PG_FUNCTION_ARGS)
  {
  	text	   *string = PG_GETARG_TEXT_PP(0);
  	int32		count = PG_GETARG_INT32(1);
  	text	   *result;
  	int			slen,
  				tlen;
  	int			i;
  	char	   *cp,
  			   *sp;
  
  	if (count < 0)
  		count = 0;
  
  	slen = VARSIZE_ANY_EXHDR(string);
  	tlen = VARHDRSZ + (count * slen);
  
  	/* Check for integer overflow */
  	if (slen != 0 && count != 0)
  	{
  		int			check = count * slen;
  		int			check2 = check + VARHDRSZ;
  
  		if ((check / slen) != count || check2 <= check)
  			ereport(ERROR,
  					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
  					 errmsg("requested length too large")));
  	}
  
  	result = (text *) palloc(tlen);
  
  	SET_VARSIZE(result, tlen);
  	cp = VARDATA(result);
  	sp = VARDATA_ANY(string);
  	for (i = 0; i < count; i++)
  	{
  		memcpy(cp, sp, slen);
  		cp += slen;
  	}
  
  	PG_RETURN_TEXT_P(result);
  }
  