Header And Logo

PostgreSQL
| The world's most advanced open source database.

Functions

oracle_compat.c File Reference

#include "postgres.h"
#include "utils/builtins.h"
#include "utils/formatting.h"
#include "mb/pg_wchar.h"
Include dependency graph for oracle_compat.c:

Go to the source code of this file.

Functions

static textdotrim (const char *string, int stringlen, const char *set, int setlen, bool doltrim, bool dortrim)
Datum lower (PG_FUNCTION_ARGS)
Datum upper (PG_FUNCTION_ARGS)
Datum initcap (PG_FUNCTION_ARGS)
Datum lpad (PG_FUNCTION_ARGS)
Datum rpad (PG_FUNCTION_ARGS)
Datum btrim (PG_FUNCTION_ARGS)
Datum btrim1 (PG_FUNCTION_ARGS)
Datum byteatrim (PG_FUNCTION_ARGS)
Datum ltrim (PG_FUNCTION_ARGS)
Datum ltrim1 (PG_FUNCTION_ARGS)
Datum rtrim (PG_FUNCTION_ARGS)
Datum rtrim1 (PG_FUNCTION_ARGS)
Datum translate (PG_FUNCTION_ARGS)
Datum ascii (PG_FUNCTION_ARGS)
Datum chr (PG_FUNCTION_ARGS)
Datum repeat (PG_FUNCTION_ARGS)

Function Documentation

Datum ascii ( PG_FUNCTION_ARGS   ) 

Definition at line 844 of file oracle_compat.c.

References Assert, encoding, ereport, errcode(), errmsg(), ERROR, GetDatabaseEncoding(), i, pg_encoding_max_length(), PG_GETARG_TEXT_PP, PG_RETURN_INT32, PG_UTF8, VARDATA_ANY, and VARSIZE_ANY_EXHDR.

Referenced by float4out(), and float8out().

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

Datum btrim ( PG_FUNCTION_ARGS   ) 

Definition at line 335 of file oracle_compat.c.

References dotrim(), PG_GETARG_TEXT_PP, PG_RETURN_TEXT_P, VARDATA_ANY, and VARSIZE_ANY_EXHDR.

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

Datum btrim1 ( PG_FUNCTION_ARGS   ) 

Definition at line 355 of file oracle_compat.c.

References dotrim(), PG_GETARG_TEXT_PP, PG_RETURN_TEXT_P, VARDATA_ANY, and VARSIZE_ANY_EXHDR.

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

Datum byteatrim ( PG_FUNCTION_ARGS   ) 

Definition at line 540 of file oracle_compat.c.

References palloc(), PG_GETARG_BYTEA_PP, PG_RETURN_BYTEA_P, SET_VARSIZE, VARDATA, VARDATA_ANY, VARHDRSZ, and VARSIZE_ANY_EXHDR.

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

Datum chr ( PG_FUNCTION_ARGS   ) 

Definition at line 925 of file oracle_compat.c.

References encoding, ereport, errcode(), errmsg(), ERROR, GetDatabaseEncoding(), palloc(), pg_encoding_max_length(), PG_GETARG_UINT32, PG_RETURN_TEXT_P, PG_UTF8, SET_VARSIZE, VARDATA, and VARHDRSZ.

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

static text * dotrim ( const char *  string,
int  stringlen,
const char *  set,
int  setlen,
bool  doltrim,
bool  dortrim 
) [static]

Definition at line 371 of file oracle_compat.c.

References cstring_to_text_with_len(), i, memcmp(), palloc(), pfree(), pg_database_encoding_max_length(), and pg_mblen().

Referenced by btrim(), btrim1(), ltrim(), ltrim1(), rtrim(), and rtrim1().

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

Datum initcap ( PG_FUNCTION_ARGS   ) 

Definition at line 108 of file oracle_compat.c.

References cstring_to_text(), pfree(), PG_GET_COLLATION, PG_GETARG_TEXT_PP, PG_RETURN_TEXT_P, str_initcap(), VARDATA_ANY, and VARSIZE_ANY_EXHDR.

{
    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),
                             PG_GET_COLLATION());
    result = cstring_to_text(out_string);
    pfree(out_string);

    PG_RETURN_TEXT_P(result);
}

Datum lower ( PG_FUNCTION_ARGS   ) 
Datum lpad ( PG_FUNCTION_ARGS   ) 

Definition at line 141 of file oracle_compat.c.

References ereport, errcode(), errmsg(), ERROR, palloc(), pg_database_encoding_max_length(), PG_GETARG_INT32, PG_GETARG_TEXT_PP, pg_mblen(), pg_mbstrlen_with_len(), PG_RETURN_TEXT_P, SET_VARSIZE, VARDATA, VARDATA_ANY, VARHDRSZ, and VARSIZE_ANY_EXHDR.

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

Datum ltrim ( PG_FUNCTION_ARGS   ) 

Definition at line 619 of file oracle_compat.c.

References dotrim(), PG_GETARG_TEXT_PP, PG_RETURN_TEXT_P, VARDATA_ANY, and VARSIZE_ANY_EXHDR.

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

Datum ltrim1 ( PG_FUNCTION_ARGS   ) 

Definition at line 639 of file oracle_compat.c.

References dotrim(), PG_GETARG_TEXT_PP, PG_RETURN_TEXT_P, VARDATA_ANY, and VARSIZE_ANY_EXHDR.

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

Datum repeat ( PG_FUNCTION_ARGS   ) 

Definition at line 1022 of file oracle_compat.c.

References ereport, errcode(), errmsg(), ERROR, i, palloc(), PG_GETARG_INT32, PG_GETARG_TEXT_PP, PG_RETURN_TEXT_P, SET_VARSIZE, VARDATA, VARDATA_ANY, VARHDRSZ, and VARSIZE_ANY_EXHDR.

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

Datum rpad ( PG_FUNCTION_ARGS   ) 

Definition at line 239 of file oracle_compat.c.

References ereport, errcode(), errmsg(), ERROR, palloc(), pg_database_encoding_max_length(), PG_GETARG_INT32, PG_GETARG_TEXT_PP, pg_mblen(), pg_mbstrlen_with_len(), PG_RETURN_TEXT_P, SET_VARSIZE, VARDATA, VARDATA_ANY, VARHDRSZ, and VARSIZE_ANY_EXHDR.

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

Datum rtrim ( PG_FUNCTION_ARGS   ) 

Definition at line 667 of file oracle_compat.c.

References dotrim(), PG_GETARG_TEXT_PP, PG_RETURN_TEXT_P, VARDATA_ANY, and VARSIZE_ANY_EXHDR.

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

Datum rtrim1 ( PG_FUNCTION_ARGS   ) 

Definition at line 687 of file oracle_compat.c.

References dotrim(), PG_GETARG_TEXT_PP, PG_RETURN_TEXT_P, VARDATA_ANY, and VARSIZE_ANY_EXHDR.

Referenced by gbt_bpchar_compress(), and gbt_bpchar_consistent().

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

Datum translate ( PG_FUNCTION_ARGS   ) 

Definition at line 718 of file oracle_compat.c.

References ereport, errcode(), errmsg(), ERROR, i, memcmp(), palloc(), pg_database_encoding_max_length(), PG_GETARG_TEXT_PP, pg_mblen(), PG_RETURN_TEXT_P, SET_VARSIZE, VARDATA, VARDATA_ANY, VARHDRSZ, and VARSIZE_ANY_EXHDR.

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

Datum upper ( PG_FUNCTION_ARGS   )