diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index df29af6371..2ff45256ea 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -3950,15 +3950,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, + the end, or both ends of bytes. + (BOTH is the default) trim('\x9012'::bytea from '\x1234567890'::bytea) @@ -3968,7 +3969,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 diff --git a/src/backend/utils/adt/oracle_compat.c b/src/backend/utils/adt/oracle_compat.c index 76e666474e..e62913d7c4 100644 --- a/src/backend/utils/adt/oracle_compat.c +++ b/src/backend/utils/adt/oracle_compat.c @@ -25,6 +25,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,27 +523,12 @@ dotrim(const char *string, int stringlen, return cstring_to_text_with_len(string, stringlen); } -/******************************************************************** - * - * byteatrim - * - * Syntax: - * - * bytea byteatrim(bytea 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) +/* + * Common implementation for bytea versions of btrim, ltrim, rtrim + */ +bytea * +dobyteatrim(bytea *string, bytea *set, bool doltrim, bool dortrim) { - bytea *string = PG_GETARG_BYTEA_PP(0); - bytea *set = PG_GETARG_BYTEA_PP(1); bytea *ret; char *ptr, *end, @@ -556,7 +543,7 @@ byteatrim(PG_FUNCTION_ARGS) setlen = VARSIZE_ANY_EXHDR(set); if (stringlen <= 0 || setlen <= 0) - PG_RETURN_BYTEA_P(string); + return string; m = stringlen; ptr = VARDATA_ANY(string); @@ -564,39 +551,124 @@ byteatrim(PG_FUNCTION_ARGS) ptr2start = VARDATA_ANY(set); end2 = ptr2start + setlen - 1; - while (m > 0) - { - ptr2 = ptr2start; - while (ptr2 <= end2) + if (doltrim) { + while (m > 0) { - if (*ptr == *ptr2) + ptr2 = ptr2start; + while (ptr2 <= end2) + { + if (*ptr == *ptr2) + break; + ++ptr2; + } + if (ptr2 > end2) break; - ++ptr2; + ptr++; + m--; } - if (ptr2 > end2) - break; - ptr++; - m--; } - while (m > 0) - { - ptr2 = ptr2start; - while (ptr2 <= end2) + if (dortrim) { + while (m > 0) { - if (*end == *ptr2) + ptr2 = ptr2start; + while (ptr2 <= end2) + { + if (*end == *ptr2) + break; + ++ptr2; + } + if (ptr2 > end2) break; - ++ptr2; + end--; + m--; } - if (ptr2 > end2) - break; - end--; - m--; } ret = (bytea *) palloc(VARHDRSZ + m); SET_VARSIZE(ret, VARHDRSZ + m); memcpy(VARDATA(ret), ptr, m); + return ret; +} + +/******************************************************************** + * + * byteatrim + * + * Syntax: + * + * bytea byteatrim(bytea 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; + + ret = dobyteatrim(string, set, true, true); + + PG_RETURN_BYTEA_P(ret); +} + +/******************************************************************** + * + * bytealtrim + * + * Syntax: + * + * bytea bytealtrim(bytea string, bytea set) + * + * Purpose: + * + * Returns string with initial characters removed up to the first + * character not in set. + * + ********************************************************************/ + +Datum +bytealtrim(PG_FUNCTION_ARGS) +{ + bytea *string = PG_GETARG_BYTEA_PP(0); + bytea *set = PG_GETARG_BYTEA_PP(1); + bytea *ret; + + ret = dobyteatrim(string, set, true, false); + + 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 c2c6df2a4f..7dfd9886a8 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9681,6 +9681,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() */ @@ -9695,6 +9696,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/pg_proc.dat b/src/include/catalog/pg_proc.dat index fc2202b843..6eec551105 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5748,6 +5748,12 @@ { oid => '2015', descr => 'trim both ends of string', proname => 'btrim', prorettype => 'bytea', proargtypes => 'bytea bytea', prosrc => 'byteatrim' }, +{ oid => '9612', descr => 'trim left ends of string', + proname => 'ltrim', prorettype => 'bytea', proargtypes => 'bytea bytea', + prosrc => 'bytealtrim' }, +{ oid => '9613', descr => 'trim right ends 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 298b6c48c2..a2bdd0dd36 100644 --- a/src/test/regress/expected/strings.out +++ b/src/test/regress/expected/strings.out @@ -2090,6 +2090,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 ad5221ab6b..49f8ee2d51 100644 --- a/src/test/regress/sql/strings.sql +++ b/src/test/regress/sql/strings.sql @@ -711,6 +711,8 @@ SELECT repeat('Pg', 4); SELECT repeat('Pg', -4); 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);