diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index 06603af4609..6a90116e42a 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -174,6 +174,7 @@
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.FORMAT_DATETIME;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.FORMAT_TIME;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.FORMAT_TIMESTAMP;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.FROM_BASE32;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.FROM_BASE64;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.ILIKE;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.JSON_DEPTH;
@@ -236,6 +237,7 @@
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.TIMESTAMP_SECONDS;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.TIMESTAMP_TRUNC;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.TIME_TRUNC;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.TO_BASE32;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.TO_BASE64;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.TO_CHAR;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.TRANSLATE3;
@@ -485,6 +487,8 @@ Builder populate() {
       defineMethod(INITCAP, BuiltInMethod.INITCAP.method, NullPolicy.STRICT);
       defineMethod(TO_BASE64, BuiltInMethod.TO_BASE64.method, NullPolicy.STRICT);
       defineMethod(FROM_BASE64, BuiltInMethod.FROM_BASE64.method, NullPolicy.STRICT);
+      defineMethod(TO_BASE32, BuiltInMethod.TO_BASE32.method, NullPolicy.STRICT);
+      defineMethod(FROM_BASE32, BuiltInMethod.FROM_BASE32.method, NullPolicy.STRICT);
       defineMethod(MD5, BuiltInMethod.MD5.method, NullPolicy.STRICT);
       defineMethod(SHA1, BuiltInMethod.SHA1.method, NullPolicy.STRICT);
       defineMethod(SHA256, BuiltInMethod.SHA256.method, NullPolicy.STRICT);
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index 486153d423b..4f2e0177e17 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -45,6 +45,7 @@
 import org.apache.calcite.util.format.FormatElement;
 import org.apache.calcite.util.format.FormatModels;
 
+import org.apache.commons.codec.binary.Base32;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.codec.language.Soundex;
 
@@ -142,6 +143,8 @@ public class SqlFunctions {
 
   private static final Pattern FROM_BASE64_REGEXP = Pattern.compile("[\\t\\n\\r\\s]");
 
+  private static final Base32 BASE_32 = new Base32();
+
   private static final Function1<List<Object>, Enumerable<Object>> LIST_AS_ENUMERABLE =
       a0 -> a0 == null ? Linq4j.emptyEnumerable() : Linq4j.asEnumerable(a0);
 
@@ -259,6 +262,25 @@ private static String toBase64_(byte[] bytes) {
     }
   }
 
+  /** SQL TO_BASE32(string) function. */
+  public static String toBase32(String string) {
+    return toBase32_(string.getBytes(UTF_8));
+  }
+
+  /** SQL TO_BASE32(string) function for binary string. */
+  public static String toBase32(ByteString string) {
+    return toBase32_(string.getBytes());
+  }
+
+  private static String toBase32_(byte[] bytes) {
+    return BASE_32.encodeToString(bytes);
+  }
+
+  /** SQL FROM_BASE32(string) function. */
+  public static ByteString fromBase32(String base32) {
+    return new ByteString(BASE_32.decode(base32));
+  }
+
   /** SQL MD5(string) function. */
   public static String md5(String string)  {
     return DigestUtils.md5Hex(string.getBytes(UTF_8));
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
index 07b150105a2..6f7485f5e02 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
@@ -469,8 +469,7 @@ static RelDataType deriveTypeSplit(SqlOperatorBinding operatorBinding,
   @LibraryOperator(libraries = {MYSQL})
   public static final SqlFunction COMPRESS =
       SqlBasicFunction.create("COMPRESS",
-          ReturnTypes.explicit(SqlTypeName.VARBINARY)
-              .andThen(SqlTypeTransforms.TO_NULLABLE),
+          ReturnTypes.VARBINARY_NULLABLE,
           OperandTypes.STRING, SqlFunctionCategory.STRING);
 
   @LibraryOperator(libraries = {MYSQL})
@@ -1225,8 +1224,7 @@ private static RelDataType deriveTypeMapFromArrays(SqlOperatorBinding opBinding)
   @LibraryOperator(libraries = {BIG_QUERY, MYSQL})
   public static final SqlFunction FROM_BASE64 =
       SqlBasicFunction.create("FROM_BASE64",
-          ReturnTypes.explicit(SqlTypeName.VARBINARY)
-              .andThen(SqlTypeTransforms.TO_NULLABLE),
+          ReturnTypes.VARBINARY_NULLABLE,
           OperandTypes.STRING, SqlFunctionCategory.STRING);
 
   @LibraryOperator(libraries = {MYSQL})
@@ -1236,6 +1234,19 @@ private static RelDataType deriveTypeMapFromArrays(SqlOperatorBinding opBinding)
           OperandTypes.STRING.or(OperandTypes.BINARY),
           SqlFunctionCategory.STRING);
 
+  @LibraryOperator(libraries = {BIG_QUERY})
+  public static final SqlFunction FROM_BASE32 =
+      SqlBasicFunction.create("FROM_BASE32",
+          ReturnTypes.VARBINARY_NULLABLE,
+          OperandTypes.CHARACTER, SqlFunctionCategory.STRING);
+
+  @LibraryOperator(libraries = {BIG_QUERY})
+  public static final SqlFunction TO_BASE32 =
+      SqlBasicFunction.create("TO_BASE32",
+          ReturnTypes.VARCHAR_NULLABLE,
+          OperandTypes.STRING,
+          SqlFunctionCategory.STRING);
+
   /** The "TO_CHAR(timestamp, format)" function;
    * converts {@code timestamp} to string according to the given {@code format}.
    */
diff --git a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
index 5b9d7aa73c2..e10107f28bc 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
@@ -481,6 +481,19 @@ public static SqlCall stripSeparator(SqlCall call) {
   public static final SqlReturnTypeInference VARCHAR_NULLABLE =
       VARCHAR.andThen(SqlTypeTransforms.TO_NULLABLE);
 
+  /**
+   * Type-inference strategy that always returns "VARBINARY".
+   */
+  public static final SqlReturnTypeInference VARBINARY =
+      ReturnTypes.explicit(SqlTypeName.VARBINARY);
+
+  /**
+   * Type-inference strategy that always returns "VARBINARY" with nulls
+   * allowed if any of the operands allow nulls.
+   */
+  public static final SqlReturnTypeInference VARBINARY_NULLABLE =
+      VARBINARY.andThen(SqlTypeTransforms.TO_NULLABLE);
+
   /**
    * Type-inference strategy for Histogram agg support.
    */
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 969cc0daf8b..ca987cc6e54 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -364,6 +364,8 @@ public enum BuiltInMethod {
   RIGHT(SqlFunctions.class, "right", String.class, int.class),
   TO_BASE64(SqlFunctions.class, "toBase64", String.class),
   FROM_BASE64(SqlFunctions.class, "fromBase64", String.class),
+  TO_BASE32(SqlFunctions.class, "toBase32", String.class),
+  FROM_BASE32(SqlFunctions.class, "fromBase32", String.class),
   MD5(SqlFunctions.class, "md5", String.class),
   SHA1(SqlFunctions.class, "sha1", String.class),
   SHA256(SqlFunctions.class, "sha256", String.class),
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index bd2e612aa3d..852559b7e0c 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -2748,6 +2748,8 @@ BigQuery's type system uses confusingly different names for types and functions:
 | b | LENGTH(string)                                 | Equivalent to `CHAR_LENGTH(string)`
 | b | LOG(numeric1 [, numeric2 ])                    | Returns the logarithm of *numeric1* to base *numeric2*, or base e if *numeric2* is not present
 | b o | LPAD(string, length [, pattern ])            | Returns a string or bytes value that consists of *string* prepended to *length* with *pattern*
+| b | TO_BASE32(string)                              | Converts the *string* to base-32 encoded form and returns an encoded string
+| b | FROM_BASE32(string)                            | Returns the decoded result of a base-32 *string* as a string
 | m | TO_BASE64(string)                              | Converts the *string* to base-64 encoded form and returns a encoded string
 | b m | FROM_BASE64(string)                          | Returns the decoded result of a base-64 *string* as a string
 | b o | LTRIM(string)                                | Returns *string* with all blanks removed from the start
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index 424f626f99f..e57c2da9dda 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -4033,6 +4033,42 @@ static void checkRlikeFails(SqlOperatorFixture f) {
     f0.forEachLibrary(list(SqlLibrary.BIG_QUERY, SqlLibrary.MYSQL), consumer);
   }
 
+  @Test void testToBase32() {
+    final SqlOperatorFixture f0 = fixture().setFor(SqlLibraryOperators.TO_BASE32);
+    f0.checkFails("^to_base32('')^",
+        "No match found for function signature TO_BASE32\\(<CHARACTER>\\)",
+        false);
+    final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.BIG_QUERY);
+    f.checkString("to_base32(x'436f6e766572747320612073657175656e6365206f6620425954"
+            + "455320696e746f2061206261736533322d656e636f64656420535452494e472e')",
+        "INXW45TFOJ2HGIDBEBZWK4LVMVXGGZJAN5TCAQSZKRCVGIDJNZ2G6IDBEBRGC43FGMZC2ZLOMNXWIZ"
+            + "LEEBJVIUSJJZDS4===",
+        "VARCHAR NOT NULL");
+    f.checkString("to_base32('Converts a sequence of BYTES into a base32-encoded STRING.')",
+        "INXW45TFOJ2HGIDBEBZWK4LVMVXGGZJAN5TCAQSZKRCVGIDJNZ2G6IDBEBRGC43FGMZC2ZLOMNXWIZ"
+            + "LEEBJVIUSJJZDS4===",
+        "VARCHAR NOT NULL");
+    f.checkNull("to_base32(cast (null as varchar))");
+    f.checkString("to_base32(x'')", "", "VARCHAR NOT NULL");
+    f.checkString("to_base32('')", "", "VARCHAR NOT NULL");
+  }
+
+  @Test void testFromBase32() {
+    final SqlOperatorFixture f0 = fixture().setFor(SqlLibraryOperators.FROM_BASE32);
+    f0.checkFails("^from_base32('')^",
+        "No match found for function signature FROM_BASE32\\(<CHARACTER>\\)",
+        false);
+    final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.BIG_QUERY);
+    f.checkString("from_base32('INXW45TFOJ2HGIDBEBZWK4LVMVXGGZJAN5TCAQSZKRCVGIDJNZ2"
+            + "G6IDBEBRGC43FGMZC2ZLOMNXWIZLEEBJVIUSJJZDS4===')",
+        "436f6e766572747320612073657175656e6365206f6620425954455320696e746f206120626173"
+            + "6533322d656e636f64656420535452494e472e",
+        "VARBINARY NOT NULL");
+
+    f.checkString("from_base32('')", "", "VARBINARY NOT NULL");
+    f.checkNull("from_base32(cast (null as varchar))");
+  }
+
   @Test void testMd5() {
     final SqlOperatorFixture f0 = fixture().setFor(SqlLibraryOperators.MD5);
     f0.checkFails("^md5(x'')^",