diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/VarbinaryFunctions.java b/core/trino-main/src/main/java/io/trino/operator/scalar/VarbinaryFunctions.java index c7f4a017b109..2dd95830e743 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/VarbinaryFunctions.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/VarbinaryFunctions.java @@ -136,6 +136,53 @@ public static Slice fromBase64UrlVarbinary(@SqlType(StandardTypes.VARBINARY) Sli } } + @Description("Encode binary data as base32") + @ScalarFunction + @SqlType(StandardTypes.VARCHAR) + public static Slice toBase32(@SqlType(StandardTypes.VARBINARY) Slice slice) + { + String encoded; + if (slice.hasByteArray()) { + encoded = BaseEncoding.base32().encode(slice.byteArray(), slice.byteArrayOffset(), slice.length()); + } + else { + encoded = BaseEncoding.base32().encode(slice.getBytes()); + } + return Slices.utf8Slice(encoded); + } + + @Description("Decode base32 encoded binary data") + @ScalarFunction("from_base32") + @LiteralParameters("x") + @SqlType(StandardTypes.VARBINARY) + public static Slice fromBase32Varchar(@SqlType("varchar(x)") Slice slice) + { + return decodeBase32(slice); + } + + @Description("Decode base32 encoded binary data") + @ScalarFunction("from_base32") + @SqlType(StandardTypes.VARBINARY) + public static Slice fromBase32Varbinary(@SqlType(StandardTypes.VARBINARY) Slice slice) + { + return decodeBase32(slice); + } + + private static Slice decodeBase32(Slice slice) + { + try { + return Slices.wrappedBuffer(BaseEncoding.base32().decode(slice.toStringUtf8())); + } + catch (IllegalArgumentException e) { + // Get cause because the root exception contains the package name in the message: + // com.google.common.io.BaseEncoding$DecodingException: Invalid input length 1 + if (e.getCause() instanceof BaseEncoding.DecodingException) { + throw new TrinoException(INVALID_FUNCTION_ARGUMENT, e.getCause().getMessage(), e); + } + throw new TrinoException(INVALID_FUNCTION_ARGUMENT, e); + } + } + @Description("Encode binary data as hex") @ScalarFunction @SqlType(StandardTypes.VARCHAR) diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestVarbinaryFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestVarbinaryFunctions.java index 2abe7a5efb38..3222a1ceaecb 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestVarbinaryFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestVarbinaryFunctions.java @@ -13,6 +13,7 @@ */ package io.trino.operator.scalar; +import com.google.common.io.BaseEncoding; import org.testng.annotations.Test; import java.util.Base64; @@ -135,6 +136,43 @@ public void testFromBase64Url() assertFunction(format("to_base64url(from_base64url('%s'))", encodeBase64Url(ALL_BYTES)), VARCHAR, encodeBase64Url(ALL_BYTES)); } + @Test + public void testToBase32() + { + assertFunction("to_base32(CAST('' AS VARBINARY))", VARCHAR, encodeBase32("")); + assertFunction("to_base32(CAST('a' AS VARBINARY))", VARCHAR, encodeBase32("a")); + assertFunction("to_base32(CAST('abc' AS VARBINARY))", VARCHAR, encodeBase32("abc")); + assertFunction("to_base32(CAST('hello world' AS VARBINARY))", VARCHAR, "NBSWY3DPEB3W64TMMQ======"); + assertFunction("to_base32(NULL)", VARCHAR, null); + } + + @Test + public void testFromBase32() + { + assertFunction("from_base32('')", VARBINARY, sqlVarbinary("")); + assertFunction("from_base32('ME======')", VARBINARY, sqlVarbinary("a")); + assertFunction("from_base32('MFRGG===')", VARBINARY, sqlVarbinary("abc")); + assertFunction("from_base32('NBSWY3DPEB3W64TMMQ======')", VARBINARY, sqlVarbinary("hello world")); + + assertFunction("from_base32(to_base32(CAST('' AS VARBINARY)))", VARBINARY, sqlVarbinary("")); + assertFunction("from_base32(to_base32(CAST('a' AS VARBINARY)))", VARBINARY, sqlVarbinary("a")); + assertFunction("from_base32(to_base32(CAST('abc' AS VARBINARY)))", VARBINARY, sqlVarbinary("abc")); + assertFunction("from_base32(to_base32(CAST('hello world' AS VARBINARY)))", VARBINARY, sqlVarbinary("hello world")); + assertFunction("from_base32(CAST(to_base32(CAST('' AS VARBINARY)) AS VARBINARY))", VARBINARY, sqlVarbinary("")); + assertFunction("from_base32(CAST(to_base32(CAST('a' AS VARBINARY)) AS VARBINARY))", VARBINARY, sqlVarbinary("a")); + assertFunction("from_base32(CAST(to_base32(CAST('abc' AS VARBINARY)) AS VARBINARY))", VARBINARY, sqlVarbinary("abc")); + assertFunction("from_base32(CAST(to_base32(CAST('hello world' AS VARBINARY)) AS VARBINARY))", VARBINARY, sqlVarbinary("hello world")); + assertFunction(format("to_base32(from_base32('%s'))", encodeBase32(ALL_BYTES)), VARCHAR, encodeBase32(ALL_BYTES)); + + assertFunction("from_base32(CAST(NULL AS VARCHAR))", VARBINARY, null); + assertFunction("from_base32(CAST(NULL AS VARBINARY))", VARBINARY, null); + + assertInvalidFunction("from_base32('1=')", "Invalid input length 1"); + assertInvalidFunction("from_base32('M1======')", "Unrecognized character: 1"); + assertInvalidFunction("from_base32(CAST('1=' AS VARBINARY))", "Invalid input length 1"); + assertInvalidFunction("from_base32(CAST('M1======' AS VARBINARY))", "Unrecognized character: 1"); + } + @Test public void testToHex() { @@ -459,6 +497,16 @@ private static String encodeBase64Url(String value) return encodeBase64Url(value.getBytes(UTF_8)); } + private static String encodeBase32(String value) + { + return encodeBase32(value.getBytes(UTF_8)); + } + + private static String encodeBase32(byte[] value) + { + return BaseEncoding.base32().encode(value); + } + private static String encodeHex(String value) { return base16().encode(value.getBytes(UTF_8)); diff --git a/docs/src/main/sphinx/functions/binary.rst b/docs/src/main/sphinx/functions/binary.rst index a984122e7541..745630157e32 100644 --- a/docs/src/main/sphinx/functions/binary.rst +++ b/docs/src/main/sphinx/functions/binary.rst @@ -81,6 +81,14 @@ The Base64 functions implement the encoding specified in :rfc:`4648`. Encodes ``binary`` into a base64 string representation using the URL safe alphabet. +.. function:: from_base32(string) -> varbinary + + Decodes binary data from the base32 encoded ``string``. + +.. function:: to_base32(binary) -> varchar + + Encodes ``binary`` into a base32 string representation. + Hex encoding functions ---------------------- diff --git a/docs/src/main/sphinx/functions/list-by-topic.rst b/docs/src/main/sphinx/functions/list-by-topic.rst index 639d1d4e0721..7607d1e5c4f2 100644 --- a/docs/src/main/sphinx/functions/list-by-topic.rst +++ b/docs/src/main/sphinx/functions/list-by-topic.rst @@ -95,6 +95,7 @@ For more details, see :doc:`binary` * ``concat()`` * :func:`crc32` +* :func:`from_base32` * :func:`from_base64` * :func:`from_base64url` * :func:`from_big_endian_32` @@ -118,6 +119,7 @@ For more details, see :doc:`binary` * :func:`spooky_hash_v2_32` * :func:`spooky_hash_v2_64` * ``substr()`` +* :func:`to_base32` * :func:`to_base64` * :func:`to_base64url` * :func:`to_big_endian_32` diff --git a/docs/src/main/sphinx/functions/list.rst b/docs/src/main/sphinx/functions/list.rst index d7847ce0fda2..19b45239661b 100644 --- a/docs/src/main/sphinx/functions/list.rst +++ b/docs/src/main/sphinx/functions/list.rst @@ -162,6 +162,7 @@ F - :func:`format_datetime` - :func:`format_number` - :func:`from_base` +- :func:`from_base32` - :func:`from_base64` - :func:`from_base64url` - :func:`from_big_endian_32` @@ -467,6 +468,7 @@ T - :func:`timezone_hour` - :func:`timezone_minute` - :func:`to_base` +- :func:`to_base32` - :func:`to_base64` - :func:`to_base64url` - :func:`to_big_endian_32`