diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index fd0370a1b4..aa99665e2e 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -3948,15 +3948,16 @@ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three'); trim - trim ( BOTH + trim ( LEADING | TRAILING | BOTH bytesremoved bytea FROM bytes bytea ) bytea Removes the longest string containing only bytes appearing in - bytesremoved from the start - and end of bytes. + bytesremoved from the start, + end, or both ends (BOTH is the default) + of bytes. trim('\x9012'::bytea from '\x1234567890'::bytea) @@ -3966,7 +3967,7 @@ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three'); - trim ( BOTH FROM + trim ( LEADING | TRAILING | BOTH FROM bytes bytea, bytesremoved bytea ) bytea @@ -4109,6 +4110,26 @@ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three'); + + + + ltrim + + ltrim ( bytes bytea, + bytesremoved bytea ) + bytea + + + Removes the longest string containing only bytes appearing in + bytesremoved from the start of + bytes. + + + ltrim('\x1234567890'::bytea, '\x9012'::bytea) + \x34567890 + + + @@ -4127,6 +4148,26 @@ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three'); + + + + rtrim + + rtrim ( bytes bytea, + bytesremoved bytea ) + bytea + + + Removes the longest string containing only bytes appearing in + bytesremoved from the end of + bytes. + + + rtrim('\x1234567890'::bytea, '\x9012'::bytea) + \x12345678 + + + diff --git a/src/backend/utils/adt/oracle_compat.c b/src/backend/utils/adt/oracle_compat.c index 99077a90b9..f737aa6fbd 100644 --- a/src/backend/utils/adt/oracle_compat.c +++ b/src/backend/utils/adt/oracle_compat.c @@ -24,6 +24,8 @@ static text *dotrim(const char *string, int stringlen, const char *set, int setlen, bool doltrim, bool dortrim); +static bytea *dobyteatrim(bytea *string, bytea *set, + bool doltrim, bool dortrim); /******************************************************************** @@ -521,6 +523,76 @@ dotrim(const char *string, int stringlen, return cstring_to_text_with_len(string, stringlen); } +/* + * Common implementation for bytea versions of btrim, ltrim, rtrim + */ +bytea * +dobyteatrim(bytea *string, bytea *set, bool doltrim, bool dortrim) +{ + 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) + return string; + + m = stringlen; + ptr = VARDATA_ANY(string); + end = ptr + stringlen - 1; + ptr2start = VARDATA_ANY(set); + end2 = ptr2start + setlen - 1; + + if (doltrim) + { + while (m > 0) + { + ptr2 = ptr2start; + while (ptr2 <= end2) + { + if (*ptr == *ptr2) + break; + ++ptr2; + } + if (ptr2 > end2) + break; + ptr++; + m--; + } + } + + if (dortrim) + { + 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); + return ret; +} + /******************************************************************** * * byteatrim @@ -543,60 +615,62 @@ 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); + ret = dobyteatrim(string, set, true, true); - if (stringlen <= 0 || setlen <= 0) - PG_RETURN_BYTEA_P(string); + PG_RETURN_BYTEA_P(ret); +} - m = stringlen; - ptr = VARDATA_ANY(string); - end = ptr + stringlen - 1; - ptr2start = VARDATA_ANY(set); - end2 = ptr2start + setlen - 1; +/******************************************************************** + * + * bytealtrim + * + * Syntax: + * + * bytea bytealtrim(bytea string, bytea set) + * + * Purpose: + * + * Returns string with initial characters removed up to the first + * character not in set. + * + ********************************************************************/ - while (m > 0) - { - ptr2 = ptr2start; - while (ptr2 <= end2) - { - if (*ptr == *ptr2) - break; - ++ptr2; - } - if (ptr2 > end2) - break; - ptr++; - m--; - } +Datum +bytealtrim(PG_FUNCTION_ARGS) +{ + bytea *string = PG_GETARG_BYTEA_PP(0); + bytea *set = PG_GETARG_BYTEA_PP(1); + bytea *ret; - while (m > 0) - { - ptr2 = ptr2start; - while (ptr2 <= end2) - { - if (*end == *ptr2) - break; - ++ptr2; - } - if (ptr2 > end2) - break; - end--; - m--; - } + ret = dobyteatrim(string, set, true, false); - ret = (bytea *) palloc(VARHDRSZ + m); - SET_VARSIZE(ret, VARHDRSZ + m); - memcpy(VARDATA(ret), ptr, m); + PG_RETURN_BYTEA_P(ret); +} + +/******************************************************************** + * + * byteartrim + * + * Syntax: + * + * bytea byteartrim(bytea string, bytea set) + * + * Purpose: + * + * Returns string with final characters removed after the last + * character not in set. + * + ********************************************************************/ + +Datum +byteartrim(PG_FUNCTION_ARGS) +{ + bytea *string = PG_GETARG_BYTEA_PP(0); + bytea *set = PG_GETARG_BYTEA_PP(1); + bytea *ret; + + ret = dobyteatrim(string, set, false, true); PG_RETURN_BYTEA_P(ret); } diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index db803b4388..8a1fbda572 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9680,6 +9680,7 @@ get_func_sql_syntax(FuncExpr *expr, deparse_context *context) appendStringInfoChar(buf, ')'); return true; + case F_LTRIM_BYTEA_BYTEA: case F_LTRIM_TEXT: case F_LTRIM_TEXT_TEXT: /* TRIM() */ @@ -9694,6 +9695,7 @@ get_func_sql_syntax(FuncExpr *expr, deparse_context *context) appendStringInfoChar(buf, ')'); return true; + case F_RTRIM_BYTEA_BYTEA: case F_RTRIM_TEXT: case F_RTRIM_TEXT_TEXT: /* TRIM() */ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 385d108c29..ee0049dc97 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202101171 +#define CATALOG_VERSION_NO 202101181 #endif diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index dd64c3bd60..b5f52d4e4a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5779,9 +5779,15 @@ { oid => '2014', descr => 'position of substring', proname => 'position', prorettype => 'int4', proargtypes => 'bytea bytea', prosrc => 'byteapos' }, -{ oid => '2015', descr => 'trim both ends of string', +{ oid => '2015', descr => 'trim selected bytes from both ends of string', proname => 'btrim', prorettype => 'bytea', proargtypes => 'bytea bytea', prosrc => 'byteatrim' }, +{ oid => '9612', descr => 'trim selected bytes from left end of string', + proname => 'ltrim', prorettype => 'bytea', proargtypes => 'bytea bytea', + prosrc => 'bytealtrim' }, +{ oid => '9613', descr => 'trim selected bytes from right end of string', + proname => 'rtrim', prorettype => 'bytea', proargtypes => 'bytea bytea', + prosrc => 'byteartrim' }, { oid => '2019', descr => 'convert timestamp with time zone to time', proname => 'time', provolatile => 's', prorettype => 'time', diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out index b234d2d4f9..bd5fe60450 100644 --- a/src/test/regress/expected/create_view.out +++ b/src/test/regress/expected/create_view.out @@ -1735,7 +1735,10 @@ select substring('foo' from 'oo') as ssf, -- historically-permitted abuse trim(' ' from ' foo ') as bt, trim(leading ' ' from ' foo ') as lt, - trim(trailing ' foo ') as rt; + trim(trailing ' foo ') as rt, + trim(E'\\000'::bytea from E'\\000Tom\\000'::bytea) as btb, + trim(leading E'\\000'::bytea from E'\\000Tom\\000'::bytea) as ltb, + trim(trailing E'\\000'::bytea from E'\\000Tom\\000'::bytea) as rtb; select pg_get_viewdef('tt201v', true); pg_get_viewdef ----------------------------------------------------------------------------------------------- @@ -1753,7 +1756,10 @@ select pg_get_viewdef('tt201v', true); "substring"('foo'::text, 'oo'::text) AS ssf, + TRIM(BOTH ' '::text FROM ' foo '::text) AS bt, + TRIM(LEADING ' '::text FROM ' foo '::text) AS lt, + - TRIM(TRAILING FROM ' foo '::text) AS rt; + TRIM(TRAILING FROM ' foo '::text) AS rt, + + TRIM(BOTH '\x00'::bytea FROM '\x00546f6d00'::bytea) AS btb, + + TRIM(LEADING '\x00'::bytea FROM '\x00546f6d00'::bytea) AS ltb, + + TRIM(TRAILING '\x00'::bytea FROM '\x00546f6d00'::bytea) AS rtb; (1 row) -- corner cases with empty join conditions diff --git a/src/test/regress/expected/strings.out b/src/test/regress/expected/strings.out index 595bd2446e..7c91afa6e4 100644 --- a/src/test/regress/expected/strings.out +++ b/src/test/regress/expected/strings.out @@ -2131,6 +2131,18 @@ SELECT trim(E'\\000'::bytea from E'\\000Tom\\000'::bytea); Tom (1 row) +SELECT trim(leading E'\\000'::bytea from E'\\000Tom\\000'::bytea); + ltrim +--------- + Tom\000 +(1 row) + +SELECT trim(trailing E'\\000'::bytea from E'\\000Tom\\000'::bytea); + rtrim +--------- + \000Tom +(1 row) + SELECT btrim(E'\\000trim\\000'::bytea, E'\\000'::bytea); btrim ------- diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql index 6d4dd53965..fbd1313b9c 100644 --- a/src/test/regress/sql/create_view.sql +++ b/src/test/regress/sql/create_view.sql @@ -605,7 +605,10 @@ select substring('foo' from 'oo') as ssf, -- historically-permitted abuse trim(' ' from ' foo ') as bt, trim(leading ' ' from ' foo ') as lt, - trim(trailing ' foo ') as rt; + trim(trailing ' foo ') as rt, + trim(E'\\000'::bytea from E'\\000Tom\\000'::bytea) as btb, + trim(leading E'\\000'::bytea from E'\\000Tom\\000'::bytea) as ltb, + trim(trailing E'\\000'::bytea from E'\\000Tom\\000'::bytea) as rtb; select pg_get_viewdef('tt201v', true); -- corner cases with empty join conditions diff --git a/src/test/regress/sql/strings.sql b/src/test/regress/sql/strings.sql index ca465d050f..ef4bfb008a 100644 --- a/src/test/regress/sql/strings.sql +++ b/src/test/regress/sql/strings.sql @@ -722,6 +722,8 @@ SELECT SUBSTRING('string'::bytea FROM -10 FOR 2147483646) AS "string"; SELECT SUBSTRING('string'::bytea FROM -10 FOR -2147483646) AS "error"; SELECT trim(E'\\000'::bytea from E'\\000Tom\\000'::bytea); +SELECT trim(leading E'\\000'::bytea from E'\\000Tom\\000'::bytea); +SELECT trim(trailing E'\\000'::bytea from E'\\000Tom\\000'::bytea); SELECT btrim(E'\\000trim\\000'::bytea, E'\\000'::bytea); SELECT btrim(''::bytea, E'\\000'::bytea); SELECT btrim(E'\\000trim\\000'::bytea, ''::bytea);