This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new cdb6522d33 [CALCITE-6444] Add a function library for Amazon Redshift
cdb6522d33 is described below

commit cdb6522d3320776f6c7ce66c01747447a39a8c84
Author: Norman Jordan <[email protected]>
AuthorDate: Fri Jun 21 13:32:51 2024 -0700

    [CALCITE-6444] Add a function library for Amazon Redshift
    
    To use the Redshift library, add 'fun=redshift' to your
    connect string. The Redshift library contains all functions
    in the Postgres library by default, unless you add
    `excludeLibraries` in the annotation on the function
    definition, and of course you can add functions that are
    Redshift-only.
    
    * A SqlLibrary can now have parent.
    * SqlLibraries will inherit functions from the parent by default.
    * An SqlOperator can specify libraries that it should be excluded from;
      it is only needed when a library might inherit the function.
    * The new Amazon Redshift function library extends the PostgreSQL
      function library.
    
    PostgreSQL function library:
    * Removed ENDS_WITH function
    
    Amazon Redshift function library is as Postgres, except:
    * Added DECODE function
    * Added NVL function
    * Added NVL2 function
    * Added GREATEST function
    * Added LEAST function
    * Added REGEXP_REPLACE
    * Added CONCAT function (only 2 arguments)
    * Added TO_CHAR function (default implementation)
    * Removed STARTS_WITH function
    * Removed ARRAY_AGG function
    * Removed ARRAY_CONCAT_AGG function
    * Removed STRING_AGG function
    * Removed ILIKE function
    * Removed NOT_ILIKE function
    * Removed CONCAT_FUNCTION_WITH_NULL function
    * Removed CONCAT function (variable arguments)
    * Removed CONCAT_WS function
    * Removed TO_CHAR (PostgreSQL implementation)
    * Removed SHA256 function
    * Removed SHA512 function
    
    Close apache/calcite#3829
---
 .../org/apache/calcite/test/BabelQuidemTest.java   |   2 +-
 .../apache/calcite/sql/fun/LibraryOperator.java    |  15 +
 .../org/apache/calcite/sql/fun/SqlLibrary.java     |  50 ++-
 .../sql/fun/SqlLibraryOperatorTableFactory.java    |  18 +
 .../calcite/sql/fun/SqlLibraryOperators.java       |  55 +--
 .../java/org/apache/calcite/util/UtilTest.java     |   8 +-
 site/_docs/reference.md                            |  73 ++--
 .../org/apache/calcite/test/SqlOperatorTest.java   | 445 ++++++++++++---------
 8 files changed, 399 insertions(+), 267 deletions(-)

diff --git a/babel/src/test/java/org/apache/calcite/test/BabelQuidemTest.java 
b/babel/src/test/java/org/apache/calcite/test/BabelQuidemTest.java
index 2ef04c9583..dff24ba13a 100644
--- a/babel/src/test/java/org/apache/calcite/test/BabelQuidemTest.java
+++ b/babel/src/test/java/org/apache/calcite/test/BabelQuidemTest.java
@@ -107,7 +107,7 @@ class BabelQuidemTest extends QuidemTest {
         case "scott-redshift":
           return CalciteAssert.that()
               .with(CalciteAssert.Config.SCOTT)
-              .with(CalciteConnectionProperty.FUN, 
"standard,postgresql,oracle")
+              .with(CalciteConnectionProperty.FUN, "standard,redshift")
               .with(CalciteConnectionProperty.PARSER_FACTORY,
                   SqlBabelParserImpl.class.getName() + "#FACTORY")
               .with(CalciteConnectionProperty.CONFORMANCE,
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/LibraryOperator.java 
b/core/src/main/java/org/apache/calcite/sql/fun/LibraryOperator.java
index d16075d1b8..f4cf369b96 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/LibraryOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/LibraryOperator.java
@@ -40,4 +40,19 @@ public @interface LibraryOperator {
   /** The set of libraries that this function or operator belongs to.
    * Must not be null or empty. */
   SqlLibrary[] libraries();
+
+  /** The set of libraries that this function should be excluded from.
+   *
+   * <p>Only needed when a library inherits functions from another library.
+   * For example, {@link SqlLibrary#REDSHIFT} inherits from
+   * {@link SqlLibrary#POSTGRESQL}, and therefore contains all of PostgreSQL's
+   * operators by default. The {@code STARTS_WITH} function is in BigQuery and
+   * PostgreSQL but not in Redshift and therefore has the annotation
+   *
+   * <blockquote>
+   *   <pre>@LibraryOperator(libraries = {BIG_QUERY, POSTGRESQL},
+   *   exceptLibraries = {REDSHIFT})</pre>
+   * </blockquote>
+   */
+  SqlLibrary[] exceptLibraries() default {};
 }
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibrary.java 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibrary.java
index 164e7cbb98..ef889928d5 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibrary.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibrary.java
@@ -20,9 +20,11 @@ import org.apache.calcite.config.CalciteConnectionProperty;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
+import java.util.Arrays;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
@@ -31,6 +33,9 @@ import java.util.Set;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import static org.apache.calcite.util.Util.filter;
+import static org.apache.calcite.util.Util.first;
+
 import static java.util.Objects.requireNonNull;
 
 /**
@@ -72,7 +77,11 @@ public enum SqlLibrary {
   /** A collection of operators that are in PostgreSQL but not in standard
    * SQL. */
   POSTGRESQL("p", "postgresql"),
-  /** A collection of operators that are in Snowflake but not in standard SQL. 
*/
+  /** A collection of operators that are in Redshift
+   * but not in standard SQL or PostgreSQL. */
+  REDSHIFT("r", "redshift", POSTGRESQL),
+  /** A collection of operators that are in Snowflake
+   * but not in standard SQL. */
   SNOWFLAKE("f", "snowflake"),
   /** A collection of operators that are in Apache Spark but not in standard
    * SQL. */
@@ -81,6 +90,10 @@ public enum SqlLibrary {
   /** Map from {@link Enum#name() name} and {@link #fun} to library. */
   public static final Map<String, SqlLibrary> MAP;
 
+  /** Map of libraries to the set of libraries whose {@link SqlLibrary#parent}
+   * link points to them. */
+  private static final Map<SqlLibrary, Set<SqlLibrary>> INHERITOR_MAP;
+
   /** Abbreviation for the library used in SQL reference. */
   public final String abbrev;
 
@@ -88,9 +101,17 @@ public enum SqlLibrary {
    * see {@link CalciteConnectionProperty#FUN}. */
   public final String fun;
 
+  /** The current library will by default inherit functions from parent. */
+  public final @Nullable SqlLibrary parent;
+
   SqlLibrary(String abbrev, String fun) {
+    this(abbrev, fun, null);
+  }
+
+  SqlLibrary(String abbrev, String fun, @Nullable SqlLibrary parent) {
     this.abbrev = requireNonNull(abbrev, "abbrev");
     this.fun = requireNonNull(fun, "fun");
+    this.parent = parent;
     checkArgument(fun.equals(name().toLowerCase(Locale.ROOT).replace("_", 
"")));
   }
 
@@ -99,12 +120,21 @@ public enum SqlLibrary {
     switch (this) {
     case ALL:
       return ImmutableList.of(BIG_QUERY, CALCITE, HIVE, MSSQL, MYSQL, ORACLE,
-          POSTGRESQL, SNOWFLAKE, SPARK);
+          POSTGRESQL, REDSHIFT, SNOWFLAKE, SPARK);
     default:
       return ImmutableList.of();
     }
   }
 
+  /** Returns the libraries that inherit this library's functions,
+   * because their {@link #parent} field points to this.
+   *
+   * <p>For example, {@link #REDSHIFT} inherits from {@link #POSTGRESQL}.
+   * Never returns null. */
+  public Set<SqlLibrary> inheritors() {
+    return first(INHERITOR_MAP.get(this), ImmutableSet.of());
+  }
+
   /** Looks up a value.
    * Returns null if not found.
    * You can use upper- or lower-case name. */
@@ -167,10 +197,20 @@ public enum SqlLibrary {
   static {
     final ImmutableMap.Builder<String, SqlLibrary> builder =
         ImmutableMap.builder();
-    for (SqlLibrary value : values()) {
-      builder.put(value.name(), value);
-      builder.put(value.fun, value);
+    final List<SqlLibrary> libraries = Arrays.asList(values());
+    for (SqlLibrary library : libraries) {
+      builder.put(library.name(), library);
+      builder.put(library.fun, library);
     }
     MAP = builder.build();
+    final ImmutableMap.Builder<SqlLibrary, Set<SqlLibrary>> map =
+        ImmutableMap.builder();
+    for (SqlLibrary library : libraries) {
+      map.put(library,
+          ImmutableSet.copyOf(
+              filter(libraries,
+                  inheritor -> inheritor.parent == library)));
+    }
+    INHERITOR_MAP = map.build();
   }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperatorTableFactory.java
 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperatorTableFactory.java
index 52e2e28128..7cea1c9bde 100644
--- 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperatorTableFactory.java
+++ 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperatorTableFactory.java
@@ -30,6 +30,7 @@ import com.google.common.collect.ImmutableSet;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 
@@ -141,6 +142,23 @@ public class SqlLibraryOperatorTableFactory {
       if (seekLibrarySet.contains(library)) {
         return true;
       }
+      // Also check inheritor libraries (if any) that are not excluded
+      for (SqlLibrary inheritor : library.inheritors()) {
+        if (seekLibrarySet.contains(inheritor)
+            && !arrayContains(libraryOperator.exceptLibraries(), inheritor)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  /** Returns whether an array contains a given element. */
+  private static <E> boolean arrayContains(E[] elements, E seek) {
+    for (E element : elements) {
+      if (Objects.equals(element, seek)) {
+        return true;
+      }
     }
     return false;
   }
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 d95713de3e..5fc393b53d 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
@@ -68,6 +68,7 @@ import static org.apache.calcite.sql.fun.SqlLibrary.MSSQL;
 import static org.apache.calcite.sql.fun.SqlLibrary.MYSQL;
 import static org.apache.calcite.sql.fun.SqlLibrary.ORACLE;
 import static org.apache.calcite.sql.fun.SqlLibrary.POSTGRESQL;
+import static org.apache.calcite.sql.fun.SqlLibrary.REDSHIFT;
 import static org.apache.calcite.sql.fun.SqlLibrary.SNOWFLAKE;
 import static org.apache.calcite.sql.fun.SqlLibrary.SPARK;
 import static org.apache.calcite.util.Static.RESOURCE;
@@ -233,7 +234,7 @@ public abstract class SqlLibraryOperators {
       };
 
   /** The "DECODE(v, v1, result1, [v2, result2, ...], resultN)" function. */
-  @LibraryOperator(libraries = {ORACLE, SPARK})
+  @LibraryOperator(libraries = {ORACLE, REDSHIFT, SPARK})
   public static final SqlFunction DECODE =
       SqlBasicFunction.create(SqlKind.DECODE, DECODE_RETURN_TYPE,
           OperandTypes.VARIADIC);
@@ -264,7 +265,7 @@ public abstract class SqlLibraryOperators {
   }
 
   /** The "NVL(value, value)" function. */
-  @LibraryOperator(libraries = {ORACLE, SPARK})
+  @LibraryOperator(libraries = {ORACLE, REDSHIFT, SPARK})
   public static final SqlBasicFunction NVL =
       SqlBasicFunction.create(SqlKind.NVL,
           ReturnTypes.LEAST_RESTRICTIVE
@@ -272,7 +273,7 @@ public abstract class SqlLibraryOperators {
           OperandTypes.SAME_SAME);
 
   /** The "NVL2(value, value, value)" function. */
-  @LibraryOperator(libraries = {ORACLE, SPARK})
+  @LibraryOperator(libraries = {ORACLE, REDSHIFT, SPARK})
   public static final SqlBasicFunction NVL2 =
       SqlBasicFunction.create(SqlKind.NVL2,
           ReturnTypes.NVL2_RESTRICTIVE
@@ -300,7 +301,7 @@ public abstract class SqlLibraryOperators {
   }
 
   /** The "LPAD(original_value, return_length[, pattern])" function. */
-  @LibraryOperator(libraries = {BIG_QUERY, ORACLE, SPARK})
+  @LibraryOperator(libraries = {BIG_QUERY, ORACLE, POSTGRESQL, SPARK})
   public static final SqlFunction LPAD =
       SqlBasicFunction.create(
           "LPAD",
@@ -309,7 +310,7 @@ public abstract class SqlLibraryOperators {
           SqlFunctionCategory.STRING);
 
   /** The "RPAD(original_value, return_length[, pattern])" function. */
-  @LibraryOperator(libraries = {BIG_QUERY, ORACLE, SPARK})
+  @LibraryOperator(libraries = {BIG_QUERY, ORACLE, POSTGRESQL, SPARK})
   public static final SqlFunction RPAD =
       SqlBasicFunction.create(
           "RPAD",
@@ -318,7 +319,7 @@ public abstract class SqlLibraryOperators {
           SqlFunctionCategory.STRING);
 
   /** The "LTRIM(string)" function. */
-  @LibraryOperator(libraries = {BIG_QUERY, ORACLE, SPARK})
+  @LibraryOperator(libraries = {BIG_QUERY, ORACLE, POSTGRESQL, SPARK})
   public static final SqlFunction LTRIM =
       SqlBasicFunction.create(SqlKind.LTRIM,
           ReturnTypes.ARG0.andThen(SqlTypeTransforms.TO_NULLABLE)
@@ -327,7 +328,7 @@ public abstract class SqlLibraryOperators {
           .withFunctionType(SqlFunctionCategory.STRING);
 
   /** The "RTRIM(string)" function. */
-  @LibraryOperator(libraries = {BIG_QUERY, ORACLE, SPARK})
+  @LibraryOperator(libraries = {BIG_QUERY, ORACLE, POSTGRESQL, SPARK})
   public static final SqlFunction RTRIM =
       SqlBasicFunction.create(SqlKind.RTRIM,
           ReturnTypes.ARG0.andThen(SqlTypeTransforms.TO_NULLABLE)
@@ -375,8 +376,8 @@ public abstract class SqlLibraryOperators {
           OperandTypes.STRING_INTEGER_OPTIONAL_INTEGER,
           SqlFunctionCategory.STRING);
 
-  /** The "ENDS_WITH(value1, value2)" function (BigQuery, PostgreSQL). */
-  @LibraryOperator(libraries = {BIG_QUERY, POSTGRESQL})
+  /** The "ENDS_WITH(value1, value2)" function (BigQuery). */
+  @LibraryOperator(libraries = {BIG_QUERY})
   public static final SqlBasicFunction ENDS_WITH =
       SqlBasicFunction.create(SqlKind.ENDS_WITH, ReturnTypes.BOOLEAN_NULLABLE,
           OperandTypes.STRING_SAME_SAME);
@@ -386,7 +387,7 @@ public abstract class SqlLibraryOperators {
   public static final SqlFunction ENDSWITH = ENDS_WITH.withName("ENDSWITH");
 
   /** The "STARTS_WITH(value1, value2)" function (BigQuery, PostgreSQL). */
-  @LibraryOperator(libraries = {BIG_QUERY, POSTGRESQL})
+  @LibraryOperator(libraries = {BIG_QUERY, POSTGRESQL}, exceptLibraries = 
{REDSHIFT})
   public static final SqlBasicFunction STARTS_WITH =
       SqlBasicFunction.create(SqlKind.STARTS_WITH, 
ReturnTypes.BOOLEAN_NULLABLE,
           OperandTypes.STRING_SAME_SAME);
@@ -445,14 +446,14 @@ public abstract class SqlLibraryOperators {
           SqlFunctionCategory.STRING);
 
   /** The "GREATEST(value, value)" function. */
-  @LibraryOperator(libraries = {BIG_QUERY, ORACLE, SPARK})
+  @LibraryOperator(libraries = {BIG_QUERY, ORACLE, REDSHIFT, SPARK})
   public static final SqlFunction GREATEST =
       SqlBasicFunction.create(SqlKind.GREATEST,
           ReturnTypes.LEAST_RESTRICTIVE.andThen(SqlTypeTransforms.TO_NULLABLE),
           OperandTypes.SAME_VARIADIC);
 
   /** The "LEAST(value, value)" function. */
-  @LibraryOperator(libraries = {BIG_QUERY, ORACLE, SPARK})
+  @LibraryOperator(libraries = {BIG_QUERY, ORACLE, REDSHIFT, SPARK})
   public static final SqlFunction LEAST =
       SqlBasicFunction.create(SqlKind.LEAST,
           ReturnTypes.LEAST_RESTRICTIVE.andThen(SqlTypeTransforms.TO_NULLABLE),
@@ -550,7 +551,7 @@ public abstract class SqlLibraryOperators {
   /** The "REGEXP_REPLACE(value, regexp, rep [, pos [, occurrence [, 
matchType]]])"
    * function. Replaces all substrings of value that match regexp with
    * {@code rep} and returns modified value. */
-  @LibraryOperator(libraries = {BIG_QUERY, MYSQL, ORACLE})
+  @LibraryOperator(libraries = {BIG_QUERY, MYSQL, ORACLE, REDSHIFT})
   public static final SqlFunction REGEXP_REPLACE = new 
SqlRegexpReplaceFunction();
 
   /** The "REGEXP_SUBSTR(value, regexp[, position[, occurrence]])" function.
@@ -672,7 +673,7 @@ public abstract class SqlLibraryOperators {
 
   /** The "ARRAY_AGG(value [ ORDER BY ...])" aggregate function,
    * in BigQuery and PostgreSQL, gathers values into arrays. */
-  @LibraryOperator(libraries = {POSTGRESQL, BIG_QUERY})
+  @LibraryOperator(libraries = {BIG_QUERY, POSTGRESQL}, exceptLibraries = 
{REDSHIFT})
   public static final SqlAggFunction ARRAY_AGG =
       SqlBasicAggFunction
           .create(SqlKind.ARRAY_AGG,
@@ -684,7 +685,7 @@ public abstract class SqlLibraryOperators {
 
   /** The "ARRAY_CONCAT_AGG(value [ ORDER BY ...])" aggregate function,
    * in BigQuery and PostgreSQL, concatenates array values into arrays. */
-  @LibraryOperator(libraries = {POSTGRESQL, BIG_QUERY})
+  @LibraryOperator(libraries = {BIG_QUERY, POSTGRESQL}, exceptLibraries = 
{REDSHIFT})
   public static final SqlAggFunction ARRAY_CONCAT_AGG =
       SqlBasicAggFunction
           .create(SqlKind.ARRAY_CONCAT_AGG, ReturnTypes.ARG0,
@@ -698,7 +699,7 @@ public abstract class SqlLibraryOperators {
    *
    * <p>{@code STRING_AGG(v, sep ORDER BY x, y)} is implemented by
    * rewriting to {@code LISTAGG(v, sep) WITHIN GROUP (ORDER BY x, y)}. */
-  @LibraryOperator(libraries = {POSTGRESQL, BIG_QUERY})
+  @LibraryOperator(libraries = {BIG_QUERY, POSTGRESQL}, exceptLibraries = 
{REDSHIFT})
   public static final SqlAggFunction STRING_AGG =
       SqlBasicAggFunction
           .create(SqlKind.STRING_AGG, ReturnTypes.ARG0_NULLABLE,
@@ -984,12 +985,12 @@ public abstract class SqlLibraryOperators {
           SqlFunctionCategory.STRING);
 
   /** The case-insensitive variant of the LIKE operator. */
-  @LibraryOperator(libraries = {POSTGRESQL})
+  @LibraryOperator(libraries = {POSTGRESQL}, exceptLibraries = {REDSHIFT})
   public static final SqlSpecialOperator ILIKE =
       new SqlLikeOperator("ILIKE", SqlKind.LIKE, false, false);
 
   /** The case-insensitive variant of the NOT LIKE operator. */
-  @LibraryOperator(libraries = {POSTGRESQL})
+  @LibraryOperator(libraries = {POSTGRESQL}, exceptLibraries = {REDSHIFT})
   public static final SqlSpecialOperator NOT_ILIKE =
       new SqlLikeOperator("NOT ILIKE", SqlKind.LIKE, true, false);
 
@@ -1033,7 +1034,7 @@ public abstract class SqlLibraryOperators {
    *
    * <p>It differs from {@link #CONCAT_FUNCTION} when processing
    * null values. */
-  @LibraryOperator(libraries = {MSSQL, POSTGRESQL})
+  @LibraryOperator(libraries = {MSSQL, POSTGRESQL}, exceptLibraries = 
{REDSHIFT})
   public static final SqlFunction CONCAT_FUNCTION_WITH_NULL =
       SqlBasicFunction.create("CONCAT",
           ReturnTypes.MULTIVALENT_STRING_SUM_PRECISION_NOT_NULLABLE,
@@ -1054,7 +1055,7 @@ public abstract class SqlLibraryOperators {
    *
    * <p>It is assigned {@link SqlKind#CONCAT2} to make it not equal to
    * {@link #CONCAT_FUNCTION}. */
-  @LibraryOperator(libraries = {ORACLE})
+  @LibraryOperator(libraries = {ORACLE, REDSHIFT})
   public static final SqlFunction CONCAT2 =
       SqlBasicFunction.create("CONCAT",
           ReturnTypes.MULTIVALENT_STRING_SUM_PRECISION_NULLABLE_ALL,
@@ -1078,7 +1079,7 @@ public abstract class SqlLibraryOperators {
    * <p>If all the arguments except the separator are null,
    * it also returns the empty string.
    * For example, {@code CONCAT_WS(',', null, null)} returns "". */
-  @LibraryOperator(libraries = {MYSQL, POSTGRESQL})
+  @LibraryOperator(libraries = {MYSQL, POSTGRESQL}, exceptLibraries = 
{REDSHIFT})
   public static final SqlFunction CONCAT_WS =
       SqlBasicFunction.create("CONCAT_WS",
           ReturnTypes.MULTIVALENT_STRING_WITH_SEP_SUM_PRECISION_ARG0_NULLABLE,
@@ -1672,7 +1673,7 @@ public abstract class SqlLibraryOperators {
    *
    * <p>({@code TO_CHAR} is not supported in MySQL, but it is supported in
    * MariaDB, a variant of MySQL covered by {@link SqlLibrary#MYSQL}.) */
-  @LibraryOperator(libraries = {MYSQL, ORACLE})
+  @LibraryOperator(libraries = {MYSQL, ORACLE, REDSHIFT})
   public static final SqlFunction TO_CHAR =
       SqlBasicFunction.create("TO_CHAR",
           ReturnTypes.VARCHAR_NULLABLE,
@@ -1681,7 +1682,7 @@ public abstract class SqlLibraryOperators {
 
   /** The "TO_CHAR(timestamp, format)" function;
    * converts {@code timestamp} to string according to the given {@code 
format}. */
-  @LibraryOperator(libraries = {POSTGRESQL})
+  @LibraryOperator(libraries = {POSTGRESQL}, exceptLibraries = {REDSHIFT})
   public static final SqlFunction TO_CHAR_PG =
       new SqlBasicFunction("TO_CHAR", SqlKind.OTHER_FUNCTION,
       SqlSyntax.FUNCTION, true, ReturnTypes.VARCHAR_NULLABLE, null,
@@ -1690,7 +1691,7 @@ public abstract class SqlLibraryOperators {
 
   /** The "TO_DATE(string1, string2)" function; casts string1
    * to a DATE using the format specified in string2. */
-  @LibraryOperator(libraries = {POSTGRESQL, ORACLE})
+  @LibraryOperator(libraries = {ORACLE, POSTGRESQL})
   public static final SqlFunction TO_DATE =
       SqlBasicFunction.create("TO_DATE",
           ReturnTypes.DATE_NULLABLE,
@@ -1699,7 +1700,7 @@ public abstract class SqlLibraryOperators {
 
   /** The "TO_TIMESTAMP(string1, string2)" function; casts string1
    * to a TIMESTAMP using the format specified in string2. */
-  @LibraryOperator(libraries = {POSTGRESQL, ORACLE})
+  @LibraryOperator(libraries = {ORACLE, POSTGRESQL})
   public static final SqlFunction TO_TIMESTAMP =
       SqlBasicFunction.create("TO_TIMESTAMP",
           ReturnTypes.TIMESTAMP_NULLABLE,
@@ -2194,14 +2195,14 @@ public abstract class SqlLibraryOperators {
           OperandTypes.STRING.or(OperandTypes.BINARY),
           SqlFunctionCategory.STRING);
 
-  @LibraryOperator(libraries = {BIG_QUERY, POSTGRESQL})
+  @LibraryOperator(libraries = {BIG_QUERY, POSTGRESQL}, exceptLibraries = 
{REDSHIFT})
   public static final SqlFunction SHA256 =
       SqlBasicFunction.create("SHA256",
           ReturnTypes.VARCHAR_NULLABLE,
           OperandTypes.STRING.or(OperandTypes.BINARY),
           SqlFunctionCategory.STRING);
 
-  @LibraryOperator(libraries = {BIG_QUERY, POSTGRESQL})
+  @LibraryOperator(libraries = {BIG_QUERY, POSTGRESQL}, exceptLibraries = 
{REDSHIFT})
   public static final SqlFunction SHA512 =
       SqlBasicFunction.create("SHA512",
           ReturnTypes.VARCHAR_NULLABLE,
diff --git a/core/src/test/java/org/apache/calcite/util/UtilTest.java 
b/core/src/test/java/org/apache/calcite/util/UtilTest.java
index 2802e561c8..324465acfd 100644
--- a/core/src/test/java/org/apache/calcite/util/UtilTest.java
+++ b/core/src/test/java/org/apache/calcite/util/UtilTest.java
@@ -951,16 +951,16 @@ class UtilTest {
 
     assertThat(SqlLibrary.expand(ImmutableList.of(a)),
         hasToString("[ALL, BIG_QUERY, CALCITE, HIVE, MSSQL, MYSQL, ORACLE, "
-            + "POSTGRESQL, SNOWFLAKE, SPARK]"));
+            + "POSTGRESQL, REDSHIFT, SNOWFLAKE, SPARK]"));
     assertThat(SqlLibrary.expand(ImmutableList.of(a, c)),
         hasToString("[ALL, BIG_QUERY, CALCITE, HIVE, MSSQL, MYSQL, ORACLE, "
-            + "POSTGRESQL, SNOWFLAKE, SPARK]"));
+            + "POSTGRESQL, REDSHIFT, SNOWFLAKE, SPARK]"));
     assertThat(SqlLibrary.expand(ImmutableList.of(c, a)),
         hasToString("[CALCITE, ALL, BIG_QUERY, HIVE, MSSQL, MYSQL, ORACLE, "
-            + "POSTGRESQL, SNOWFLAKE, SPARK]"));
+            + "POSTGRESQL, REDSHIFT, SNOWFLAKE, SPARK]"));
     assertThat(SqlLibrary.expand(ImmutableList.of(c, o, a)),
         hasToString("[CALCITE, ORACLE, ALL, BIG_QUERY, HIVE, MSSQL, MYSQL, "
-            + "POSTGRESQL, SNOWFLAKE, SPARK]"));
+            + "POSTGRESQL, REDSHIFT, SNOWFLAKE, SPARK]"));
     assertThat(SqlLibrary.expand(ImmutableList.of(o, c, o)),
         hasToString("[ORACLE, CALCITE]"));
 
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 2b7777d790..1b83fd99e1 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -2665,6 +2665,7 @@ The 'C' (compatibility) column contains value:
 * 'q' for Microsoft SQL Server ('fun=mssql' in the connect string),
 * 'o' for Oracle ('fun=oracle' in the connect string),
 * 'p' for PostgreSQL ('fun=postgresql' in the connect string),
+* 'r' for Amazon RedShift ('fun=redshift' in the connect string),
 * 's' for Apache Spark ('fun=spark' in the connect string).
 
 One operator name may correspond to multiple SQL dialects, but with different
@@ -2722,10 +2723,10 @@ In the following:
 | s | BIT_GET(value, position)                       | Returns the bit (0 or 
1) value at the specified *position* of numeric *value*. The positions are 
numbered from right to left, starting at zero. The *position* argument cannot 
be negative
 | b | CEIL(value)                                    | Similar to standard 
`CEIL(value)` except if *value* is an integer type, the return type is a double
 | m s | CHAR(integer)                                | Returns the character 
whose ASCII code is *integer* % 256, or null if *integer* &lt; 0
-| b o p | CHR(integer)                               | Returns the character 
whose UTF-8 code is *integer*
+| b o p r | CHR(integer)                             | Returns the character 
whose UTF-8 code is *integer*
 | b | CODE_POINTS_TO_BYTES(integers)                 | Converts *integers*, an 
array of integers between 0 and 255 inclusive, into bytes; throws error if any 
element is out of range
 | b | CODE_POINTS_TO_STRING(integers)                | Converts *integers*, an 
array of integers between 0 and 0xD7FF or between 0xE000 and 0x10FFFF 
inclusive, into string; throws error if any element is out of range
-| o | CONCAT(string, string)                         | Concatenates two 
strings, returns null only when both string arguments are null, otherwise 
treats null as empty string
+| o r | CONCAT(string, string)                       | Concatenates two 
strings, returns null only when both string arguments are null, otherwise 
treats null as empty string
 | b m | CONCAT(string [, string ]*)                  | Concatenates one or 
more strings, returns null if any of the arguments is null
 | p q | CONCAT(string [, string ]*)                  | Concatenates one or 
more strings, null is treated as empty string
 | m p | CONCAT_WS(separator, str1 [, string ]*)      | Concatenates one or 
more strings, returns null only when separator is null, otherwise treats null 
arguments as empty strings
@@ -2733,7 +2734,7 @@ In the following:
 | m | COMPRESS(string)                               | Compresses a string 
using zlib compression and returns the result as a binary string
 | b | CONTAINS_SUBSTR(expression, string [ , json_scope =&gt; json_scope_value 
]) | Returns whether *string* exists as a substring in *expression*. Optional 
*json_scope* argument specifies what scope to search if *expression* is in JSON 
format. Returns NULL if a NULL exists in *expression* that does not result in a 
match
 | q | CONVERT(type, expression [ , style ])          | Equivalent to 
`CAST(expression AS type)`; ignores the *style* operand
-| p | CONVERT_TIMEZONE(tz1, tz2, datetime)           | Converts the timezone 
of *datetime* from *tz1* to *tz2*
+| p r | CONVERT_TIMEZONE(tz1, tz2, datetime)         | Converts the timezone 
of *datetime* from *tz1* to *tz2*
 | * | COSH(numeric)                                  | Returns the hyperbolic 
cosine of *numeric*
 | * | COTH(numeric)                                  | Returns the hyperbolic 
cotangent of *numeric*
 | * | CSC(numeric)                                   | Returns the cosecant of 
*numeric* in radians
@@ -2745,8 +2746,8 @@ In the following:
 | b | DATE(timestampLtz, timeZone)                   | Extracts the DATE from 
*timestampLtz* (an instant; BigQuery's TIMESTAMP type) in *timeZone*
 | b | DATE(string)                                   | Equivalent to 
`CAST(string AS DATE)`
 | b | DATE(year, month, day)                         | Returns a DATE value 
for *year*, *month*, and *day* (all of type INTEGER)
-| p q | DATEADD(timeUnit, integer, datetime)         | Equivalent to 
`TIMESTAMPADD(timeUnit, integer, datetime)`
-| p q | DATEDIFF(timeUnit, datetime, datetime2)      | Equivalent to 
`TIMESTAMPDIFF(timeUnit, datetime, datetime2)`
+| p q r | DATEADD(timeUnit, integer, datetime)       | Equivalent to 
`TIMESTAMPADD(timeUnit, integer, datetime)`
+| p q r | DATEDIFF(timeUnit, datetime, datetime2)    | Equivalent to 
`TIMESTAMPDIFF(timeUnit, datetime, datetime2)`
 | q | DATEPART(timeUnit, datetime)                   | Equivalent to 
`EXTRACT(timeUnit FROM  datetime)`
 | b | DATETIME(date, time)                           | Converts *date* and 
*time* to a TIMESTAMP
 | b | DATETIME(date)                                 | Converts *date* to a 
TIMESTAMP value (at midnight)
@@ -2757,15 +2758,15 @@ In the following:
 | b | DATETIME_SUB(timestamp, interval)              | Returns the TIMESTAMP 
that occurs *interval* before *timestamp*
 | b | DATETIME_TRUNC(timestamp, timeUnit)            | Truncates *timestamp* 
to the granularity of *timeUnit*, rounding to the beginning of the unit
 | b s | DATE_FROM_UNIX_DATE(integer)                 | Returns the DATE that 
is *integer* days after 1970-01-01
-| p | DATE_PART(timeUnit, datetime)                  | Equivalent to 
`EXTRACT(timeUnit FROM  datetime)`
+| p r | DATE_PART(timeUnit, datetime)                | Equivalent to 
`EXTRACT(timeUnit FROM  datetime)`
 | b | DATE_ADD(date, interval)                       | Returns the DATE value 
that occurs *interval* after *date*
 | b | DATE_DIFF(date, date2, timeUnit)               | Returns the whole 
number of *timeUnit* between *date* and *date2*
 | b | DATE_SUB(date, interval)                       | Returns the DATE value 
that occurs *interval* before *date*
 | b | DATE_TRUNC(date, timeUnit)                     | Truncates *date* to the 
granularity of *timeUnit*, rounding to the beginning of the unit
-| o s | DECODE(value, value1, result1 [, valueN, resultN ]* [, default ]) | 
Compares *value* to each *valueN* value one by one; if *value* is equal to a 
*valueN*, returns the corresponding *resultN*, else returns *default*, or NULL 
if *default* is not specified
-| p | DIFFERENCE(string, string)                     | Returns a measure of 
the similarity of two strings, namely the number of character positions that 
their `SOUNDEX` values have in common: 4 if the `SOUNDEX` values are same and 0 
if the `SOUNDEX` values are totally different
+| o r s | DECODE(value, value1, result1 [, valueN, resultN ]* [, default ]) | 
Compares *value* to each *valueN* value one by one; if *value* is equal to a 
*valueN*, returns the corresponding *resultN*, else returns *default*, or NULL 
if *default* is not specified
+| p r | DIFFERENCE(string, string)                   | Returns a measure of 
the similarity of two strings, namely the number of character positions that 
their `SOUNDEX` values have in common: 4 if the `SOUNDEX` values are same and 0 
if the `SOUNDEX` values are totally different
 | f s | ENDSWITH(string1, string2)                   | Returns whether 
*string2* is a suffix of *string1*
-| b p | ENDS_WITH(string1, string2)                  | Equivalent to 
`ENDSWITH(string1, string2)`
+| b | ENDS_WITH(string1, string2)                    | Equivalent to 
`ENDSWITH(string1, string2)`
 | s | EXISTS(array, func)                            | Returns whether a 
predicate *func* holds for one or more elements in the *array*
 | o | EXISTSNODE(xml, xpath, [, namespaces ])        | Determines whether 
traversal of a XML document using a specified xpath results in any nodes. 
Returns 0 if no nodes remain after applying the XPath traversal on the document 
fragment of the element or elements matched by the XPath expression. Returns 1 
if any nodes remain. The optional namespace value that specifies a default 
mapping or namespace mapping for prefixes, which is used when evaluating the 
XPath expression.
 | o | EXTRACT(xml, xpath, [, namespaces ])           | Returns the XML 
fragment of the element or elements matched by the XPath expression. The 
optional namespace value that specifies a default mapping or namespace mapping 
for prefixes, which is used when evaluating the XPath expression
@@ -2780,13 +2781,13 @@ In the following:
 | b | FORMAT_TIME(string, time)                      | Formats *time* 
according to the specified format *string*
 | b | FORMAT_TIMESTAMP(string timestamp)             | Formats *timestamp* 
according to the specified format *string*
 | s | GETBIT(value, position)                        | Equivalent to 
`BIT_GET(value, position)`
-| b o s | GREATEST(expr [, expr ]*)                  | Returns the greatest of 
the expressions
+| b o r s | GREATEST(expr [, expr ]*)                | Returns the greatest of 
the expressions
 | b h s | IF(condition, value1, value2)              | Returns *value1* if 
*condition* is TRUE, *value2* otherwise
 | b s | IFNULL(value1, value2)                       | Equivalent to 
`NVL(value1, value2)`
 | p | string1 ILIKE string2 [ ESCAPE string3 ]       | Whether *string1* 
matches pattern *string2*, ignoring case (similar to `LIKE`)
 | p | string1 NOT ILIKE string2 [ ESCAPE string3 ]   | Whether *string1* does 
not match pattern *string2*, ignoring case (similar to `NOT LIKE`)
 | b o | INSTR(string, substring [, from [, occurrence ] ]) | Returns the 
position of *substring* in *string*, searching starting at *from* (default 1), 
and until locating the nth *occurrence* (default 1) of *substring*
-| m | INSTR(string, substring)                       | Equivalent to 
`POSITION(substring IN string)`
+| b m o | INSTR(string, substring)                   | Equivalent to 
`POSITION(substring IN string)`
 | b | IS_INF(value)                                  | Returns whether *value* 
is infinite
 | b | IS_NAN(value)                                  | Returns whether *value* 
is NaN
 | m | JSON_TYPE(jsonValue)                           | Returns a string value 
indicating the type of *jsonValue*
@@ -2799,21 +2800,21 @@ In the following:
 | m | JSON_REPLACE(jsonValue, path, val [, path, val ]*)  | Returns a JSON 
document replace a data of *jsonValue*, *path*, *val*
 | m | JSON_SET(jsonValue, path, val [, path, val ]*) | Returns a JSON document 
set a data of *jsonValue*, *path*, *val*
 | m | JSON_STORAGE_SIZE(jsonValue)                   | Returns the number of 
bytes used to store the binary representation of *jsonValue*
-| b o s | LEAST(expr [, expr ]* )                    | Returns the least of 
the expressions
-| b m p s | LEFT(string, length)                     | Returns the leftmost 
*length* characters from the *string*
+| b o r s | LEAST(expr [, expr ]* )                  | Returns the least of 
the expressions
+| b m p r s | LEFT(string, length)                   | Returns the leftmost 
*length* characters from the *string*
 | f s | LEN(string)                                  | Equivalent to 
`CHAR_LENGTH(string)`
 | b f s | LENGTH(string)                             | Equivalent to 
`CHAR_LENGTH(string)`
 | h s | LEVENSHTEIN(string1, string2)                | Returns the Levenshtein 
distance between *string1* and *string2*
 | b | LOG(numeric1 [, numeric2 ])                    | Returns the logarithm 
of *numeric1* to base *numeric2*, or base e if *numeric2* is not present
 | m s | LOG2(numeric)                                | Returns the base 2 
logarithm of *numeric*
-| b o s | LPAD(string, length [, pattern ])          | Returns a string or 
bytes value that consists of *string* prepended to *length* with *pattern*
+| b o p r s | 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 | TO_HEX(binary)                                 | Converts *binary* into 
a hexadecimal varchar
 | b | FROM_HEX(varchar)                              | Converts a 
hexadecimal-encoded *varchar* into bytes
-| b o s | LTRIM(string)                              | Returns *string* with 
all blanks removed from the start
+| b o p r s | LTRIM(string)                          | Returns *string* with 
all blanks removed from the start
 | s | MAP()                                          | Returns an empty map
 | s | MAP(key, value [, key, value]*)                | Returns a map with the 
given *key*/*value* pairs
 | s | MAP_CONCAT(map [, map]*)                       | Concatenates one or 
more maps. If any input argument is `NULL` the function returns `NULL`. Note 
that calcite is using the LAST_WIN strategy
@@ -2824,10 +2825,10 @@ In the following:
 | s | MAP_FROM_ARRAYS(array1, array2)                | Returns a map created 
from an *array1* and *array2*. Note that the lengths of two arrays should be 
the same and calcite is using the LAST_WIN strategy
 | s | MAP_FROM_ENTRIES(arrayOfRows)                  | Returns a map created 
from an arrays of row with two fields. Note that the number of fields in a row 
must be 2. Note that calcite is using the LAST_WIN strategy
 | s | STR_TO_MAP(string [, stringDelimiter [, keyValueDelimiter]]) | Returns a 
map after splitting the *string* into key/value pairs using delimiters. Default 
delimiters are ',' for *stringDelimiter* and ':' for *keyValueDelimiter*. Note 
that calcite is using the LAST_WIN strategy
-| b m p s | MD5(string)                              | Calculates an MD5 
128-bit checksum of *string* and returns it as a hex string
+| b m p r s | MD5(string)                            | Calculates an MD5 
128-bit checksum of *string* and returns it as a hex string
 | m | MONTHNAME(date)                                | Returns the name, in 
the connection's locale, of the month in *datetime*; for example, it returns 
'二月' for both DATE '2020-02-10' and TIMESTAMP '2020-02-10 10:10:10'
-| o s | NVL(value1, value2)                          | Returns *value1* if 
*value1* is not null, otherwise *value2*
-| o s | NVL2(value1, value2, value3)                 | Returns *value2* if 
*value1* is not null, otherwise *value3*
+| o r s | NVL(value1, value2)                        | Returns *value1* if 
*value1* is not null, otherwise *value2*
+| o r s | NVL2(value1, value2, value3)               | Returns *value2* if 
*value1* is not null, otherwise *value3*
 | b | OFFSET(index)                                  | When indexing an array, 
wrapping *index* in `OFFSET` returns the value at the 0-based *index*; throws 
error if *index* is out of bounds
 | b | ORDINAL(index)                                 | Similar to `OFFSET` 
except *index* begins at 1
 | b | PARSE_DATE(format, string)                     | Uses format specified 
by *format* to convert *string* representation of date to a DATE value
@@ -2836,23 +2837,23 @@ In the following:
 | b | PARSE_TIMESTAMP(format, string[, timeZone])    | Uses format specified 
by *format* to convert *string* representation of timestamp to a TIMESTAMP WITH 
LOCAL TIME ZONE value in *timeZone*
 | h s | PARSE_URL(urlString, partToExtract [, keyToExtract] ) | Returns the 
specified *partToExtract* from the *urlString*. Valid values for 
*partToExtract* include HOST, PATH, QUERY, REF, PROTOCOL, AUTHORITY, FILE, and 
USERINFO. *keyToExtract* specifies which query to extract
 | b s | POW(numeric1, numeric2)                      | Returns *numeric1* 
raised to the power *numeric2*
-| b c h q m o f s p | POWER(numeric1, numeric2) | Returns *numeric1* raised to 
the power of *numeric2*
-| p | RANDOM()                                       | Generates a random 
double between 0 and 1 inclusive
+| b c h q m o f s p r | POWER(numeric1, numeric2) | Returns *numeric1* raised 
to the power of *numeric2*
+| p r | RANDOM()                                     | Generates a random 
double between 0 and 1 inclusive
 | s | REGEXP(string, regexp)                         | Equivalent to `string1 
RLIKE string2`
 | b | REGEXP_CONTAINS(string, regexp)                | Returns whether 
*string* is a partial match for the *regexp*
 | b | REGEXP_EXTRACT(string, regexp [, position [, occurrence]]) | Returns the 
substring in *string* that matches the *regexp*, starting search at *position* 
(default 1), and until locating the nth *occurrence* (default 1). Returns NULL 
if there is no match
 | b | REGEXP_EXTRACT_ALL(string, regexp)             | Returns an array of all 
substrings in *string* that matches the *regexp*. Returns an empty array if 
there is no match
 | b | REGEXP_INSTR(string, regexp [, position [, occurrence [, 
occurrence_position]]]) | Returns the lowest 1-based position of the substring 
in *string* that matches the *regexp*, starting search at *position* (default 
1), and until locating the nth *occurrence* (default 1). Setting 
occurrence_position (default 0) to 1 returns the end position of substring + 1. 
Returns 0 if there is no match
-| m o p s | REGEXP_LIKE(string, regexp [, flags])        | Equivalent to 
`string1 RLIKE string2` with an optional parameter for search flags. Supported 
flags are: <ul><li>i: case-insensitive matching</li><li>c: case-sensitive 
matching</li><li>n: newline-sensitive matching</li><li>s: non-newline-sensitive 
matching</li><li>m: multi-line</li></ul>
-| b m o | REGEXP_REPLACE(string, regexp, rep [, pos [, occurrence [, 
matchType]]]) | Replaces all substrings of *string* that match *regexp* with 
*rep* at the starting *pos* in expr (if omitted, the default is 1), 
*occurrence* specifies which occurrence of a match to search for (if omitted, 
the default is 1), *matchType* specifies how to perform matching
+| m o p r s | REGEXP_LIKE(string, regexp [, flags])  | Equivalent to `string1 
RLIKE string2` with an optional parameter for search flags. Supported flags 
are: <ul><li>i: case-insensitive matching</li><li>c: case-sensitive 
matching</li><li>n: newline-sensitive matching</li><li>s: non-newline-sensitive 
matching</li><li>m: multi-line</li></ul>
+| b m o r | REGEXP_REPLACE(string, regexp, rep [, pos [, occurrence [, 
matchType]]]) | Replaces all substrings of *string* that match *regexp* with 
*rep* at the starting *pos* in expr (if omitted, the default is 1), 
*occurrence* specifies which occurrence of a match to search for (if omitted, 
the default is 1), *matchType* specifies how to perform matching
 | b | REGEXP_SUBSTR(string, regexp [, position [, occurrence]]) | Synonym for 
REGEXP_EXTRACT
-| b m p s | REPEAT(string, integer)                  | Returns a string 
consisting of *string* repeated of *integer* times; returns an empty string if 
*integer* is less than 1
+| b m p r s | REPEAT(string, integer)                | Returns a string 
consisting of *string* repeated of *integer* times; returns an empty string if 
*integer* is less than 1
 | b m | REVERSE(string)                              | Returns *string* with 
the order of the characters reversed
-| b m p s | RIGHT(string, length)                    | Returns the rightmost 
*length* characters from the *string*
+| b m p r s | RIGHT(string, length)                  | Returns the rightmost 
*length* characters from the *string*
 | h m s | string1 RLIKE string2                      | Whether *string1* 
matches regex pattern *string2* (similar to `LIKE`, but uses Java regex)
 | h m s | string1 NOT RLIKE string2                  | Whether *string1* does 
not match regex pattern *string2* (similar to `NOT LIKE`, but uses Java regex)
-| b o s | RPAD(string, length[, pattern ])           | Returns a string or 
bytes value that consists of *string* appended to *length* with *pattern*
-| b o s | RTRIM(string)                              | Returns *string* with 
all blanks removed from the end
+| b o p r s | RPAD(string, length[, pattern ])       | Returns a string or 
bytes value that consists of *string* appended to *length* with *pattern*
+| b o p r s | RTRIM(string)                          | Returns *string* with 
all blanks removed from the end
 | b | SAFE_ADD(numeric1, numeric2)                   | Returns *numeric1* + 
*numeric2*, or NULL on overflow.  Arguments are implicitly cast to one of the 
types BIGINT, DOUBLE, or DECIMAL
 | b | SAFE_CAST(value AS type)                       | Converts *value* to 
*type*, returning NULL if conversion fails
 | b | SAFE_DIVIDE(numeric1, numeric2)                | Returns *numeric1* / 
*numeric2*, or NULL on overflow or if *numeric2* is zero.  Arguments implicitly 
are cast to one of the types BIGINT, DOUBLE, or DECIMAL
@@ -2863,19 +2864,19 @@ In the following:
 | b | SAFE_SUBTRACT(numeric1, numeric2)              | Returns *numeric1* - 
*numeric2*, or NULL on overflow.  Arguments are implicitly cast to one of the 
types BIGINT, DOUBLE, or DECIMAL
 | * | SEC(numeric)                                   | Returns the secant of 
*numeric* in radians
 | * | SECH(numeric)                                  | Returns the hyperbolic 
secant of *numeric*
-| b m p s | SHA1(string)                             | Calculates a SHA-1 hash 
value of *string* and returns it as a hex string
+| b m p r s | SHA1(string)                           | Calculates a SHA-1 hash 
value of *string* and returns it as a hex string
 | b p | SHA256(string)                               | Calculates a SHA-256 
hash value of *string* and returns it as a hex string
 | b p | SHA512(string)                               | Calculates a SHA-512 
hash value of *string* and returns it as a hex string
 | * | SINH(numeric)                                  | Returns the hyperbolic 
sine of *numeric*
-| b m o p | SOUNDEX(string)                          | Returns the phonetic 
representation of *string*; throws if *string* is encoded with multi-byte 
encoding such as UTF-8
+| b m o p r | SOUNDEX(string)                        | Returns the phonetic 
representation of *string*; throws if *string* is encoded with multi-byte 
encoding such as UTF-8
 | s | SOUNDEX(string)                                | Returns the phonetic 
representation of *string*; return original *string* if *string* is encoded 
with multi-byte encoding such as UTF-8
 | m s | SPACE(integer)                               | Returns a string of 
*integer* spaces; returns an empty string if *integer* is less than 1
 | b | SPLIT(string [, delimiter ])                   | Returns the string 
array of *string* split at *delimiter* (if omitted, default is comma).  If the 
*string* is empty it returns an empty array, otherwise, if the *delimiter* is 
empty, it returns an array containing the original *string*.
 | f s | STARTSWITH(string1, string2)                 | Returns whether 
*string2* is a prefix of *string1*
 | b p | STARTS_WITH(string1, string2)                | Equivalent to 
`STARTSWITH(string1, string2)`
 | m | STRCMP(string, string)                         | Returns 0 if both of 
the strings are same and returns -1 when the first argument is smaller than the 
second and 1 when the second one is smaller than the first one
-| b p | STRPOS(string, substring)                    | Equivalent to 
`POSITION(substring IN string)`
-| b m o p | SUBSTR(string, position [, substringLength ]) | Returns a portion 
of *string*, beginning at character *position*, *substringLength* characters 
long. SUBSTR calculates lengths using characters as defined by the input 
character set
+| b r p | STRPOS(string, substring)                  | Equivalent to 
`POSITION(substring IN string)`
+| b m o p r | SUBSTR(string, position [, substringLength ]) | Returns a 
portion of *string*, beginning at character *position*, *substringLength* 
characters long. SUBSTR calculates lengths using characters as defined by the 
input character set
 | * | TANH(numeric)                                  | Returns the hyperbolic 
tangent of *numeric*
 | b | TIME(hour, minute, second)                     | Returns a TIME value 
*hour*, *minute*, *second* (all of type INTEGER)
 | b | TIME(timestamp)                                | Extracts the TIME from 
*timestamp* (a local time; BigQuery's DATETIME type)
@@ -2898,11 +2899,11 @@ In the following:
 | b | TIME_DIFF(time, time2, timeUnit)               | Returns the whole 
number of *timeUnit* between *time* and *time2*
 | b | TIME_SUB(time, interval)                       | Returns the TIME value 
that is *interval* before *time*
 | b | TIME_TRUNC(time, timeUnit)                     | Truncates *time* to the 
granularity of *timeUnit*, rounding to the beginning of the unit
-| m o p | TO_CHAR(timestamp, format)                 | Converts *timestamp* to 
a string using the format *format*
+| m o p r | TO_CHAR(timestamp, format)               | Converts *timestamp* to 
a string using the format *format*
 | b | TO_CODE_POINTS(string)                         | Converts *string* to an 
array of integers that represent code points or extended ASCII character values
-| o p | TO_DATE(string, format)                      | Converts *string* to a 
date using the format *format*
-| o p | TO_TIMESTAMP(string, format)                 | Converts *string* to a 
timestamp using the format *format*
-| b o p s | TRANSLATE(expr, fromString, toString)    | Returns *expr* with all 
occurrences of each character in *fromString* replaced by its corresponding 
character in *toString*. Characters in *expr* that are not in *fromString* are 
not replaced
+| o p r | TO_DATE(string, format)                    | Converts *string* to a 
date using the format *format*
+| o p r | TO_TIMESTAMP(string, format)               | Converts *string* to a 
timestamp using the format *format*
+| b o p r s | TRANSLATE(expr, fromString, toString)  | Returns *expr* with all 
occurrences of each character in *fromString* replaced by its corresponding 
character in *toString*. Characters in *expr* that are not in *fromString* are 
not replaced
 | b | TRUNC(numeric1 [, integer2 ])                  | Truncates *numeric1* to 
optionally *integer2* (if not specified 0) places right to the decimal point
 | q | TRY_CAST(value AS type)                        | Converts *value* to 
*type*, returning NULL if conversion fails
 | b s | UNIX_MICROS(timestamp)                       | Returns the number of 
microseconds since 1970-01-01 00:00:00
@@ -2948,8 +2949,8 @@ Dialect-specific aggregate functions.
 | c | AGGREGATE(m)                                   | Computes measure *m* in 
the context of the current GROUP BY key
 | b p | ARRAY_AGG( [ ALL &#124; DISTINCT ] value [ RESPECT NULLS &#124; IGNORE 
NULLS ] [ ORDER BY orderItem [, orderItem ]* ] ) | Gathers values into arrays
 | b p | ARRAY_CONCAT_AGG( [ ALL &#124; DISTINCT ] value [ ORDER BY orderItem 
[, orderItem ]* ] ) | Concatenates arrays into arrays
-| p s | BOOL_AND(condition)                          | Synonym for `EVERY`
-| p s | BOOL_OR(condition)                           | Synonym for `SOME`
+| p r s | BOOL_AND(condition)                        | Synonym for `EVERY`
+| p r s | BOOL_OR(condition)                         | Synonym for `SOME`
 | f | BOOLAND_AGG(condition)                         | Synonym for `EVERY`
 | f | BOOLOR_AGG(condition)                          | Synonym for `SOME`
 | b | COUNTIF(condition)                             | Returns the number of 
rows for which *condition* is TRUE; equivalent to `COUNT(*) FILTER (WHERE 
condition)`
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 d018b5de76..094e8eda09 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -2117,8 +2117,9 @@ public class SqlOperatorTest {
       f.checkScalar("chr(0)", String.valueOf('\u0000'), "CHAR(1) NOT NULL");
       f.checkNull("chr(null)");
     };
-    f0.forEachLibrary(list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, 
SqlLibrary.POSTGRESQL),
-        consumer);
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, SqlLibrary.POSTGRESQL);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testCodePointsToBytes() {
@@ -2368,6 +2369,7 @@ public class SqlOperatorTest {
     checkConcatFuncWithNull(f.withLibrary(SqlLibrary.POSTGRESQL));
     checkConcatFuncWithNull(f.withLibrary(SqlLibrary.MSSQL));
     checkConcat2Func(f.withLibrary(SqlLibrary.ORACLE));
+    checkConcat2Func(f.withLibrary(SqlLibrary.REDSHIFT));
   }
 
   private static void checkConcatFunc(SqlOperatorFixture f) {
@@ -3802,6 +3804,7 @@ public class SqlOperatorTest {
     checkRlikeFunc(f, SqlLibrary.POSTGRESQL, SqlLibraryOperators.REGEXP_LIKE);
     checkRlikeFunc(f, SqlLibrary.MYSQL, SqlLibraryOperators.REGEXP_LIKE);
     checkRlikeFunc(f, SqlLibrary.ORACLE, SqlLibraryOperators.REGEXP_LIKE);
+    checkRlikeFunc(f, SqlLibrary.REDSHIFT, SqlLibraryOperators.REGEXP_LIKE);
   }
 
   void checkRlikeFunc(SqlOperatorFixture f0, SqlLibrary library, SqlOperator 
operator) {
@@ -4004,9 +4007,10 @@ public class SqlOperatorTest {
       f1.checkNull("REGEXP_LIKE('atest\nstr', NULL, 'sn')");
       f1.checkNull("REGEXP_LIKE('atest\nstr', 'test.str', NULL)");
     };
-    f.forEachLibrary(
-        list(SqlLibrary.MYSQL, SqlLibrary.SPARK,
-        SqlLibrary.POSTGRESQL, SqlLibrary.ORACLE), consumer);
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.MYSQL, SqlLibrary.SPARK, SqlLibrary.POSTGRESQL,
+            SqlLibrary.ORACLE);
+    f.forEachLibrary(libraries, consumer);
   }
 
   /** Test case for
@@ -4322,8 +4326,8 @@ public class SqlOperatorTest {
       f.checkNull("translate('aabbcc', 'ab', cast(null as varchar(2)))");
     };
     final List<SqlLibrary> libraries =
-        ImmutableList.of(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE,
-            SqlLibrary.POSTGRESQL, SqlLibrary.SPARK);
+        list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, SqlLibrary.POSTGRESQL,
+            SqlLibrary.REDSHIFT, SqlLibrary.SPARK);
     f0.forEachLibrary(libraries, consumer);
   }
 
@@ -4719,83 +4723,88 @@ public class SqlOperatorTest {
   }
 
   @Test void testToChar() {
-    final SqlOperatorFixture f = fixture().withLibrary(SqlLibrary.MYSQL);
-    f.setFor(SqlLibraryOperators.TO_CHAR);
-    f.checkString("to_char(timestamp '2022-06-03 12:15:48.678', 'YYYY-MM-DD 
HH24:MI:SS.MS TZ')",
-        "2022-06-03 12:15:48.678",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 12:15:48.678', 'Day')",
-        "Friday",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '0001-01-01 00:00:00.000', 'Day')",
-        "Monday",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 12:15:48.678', 'DY')",
-        "FRI",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '0001-01-01 00:00:00.000', 'DY')",
-        "MON",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 12:15:48.678', 'CC')",
-        "21",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'HH12')",
-        "01",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'HH24')",
-        "13",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'MI')",
-        "15",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'MS')",
-        "678",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'Q')",
-        "2",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'IW')",
-        "23",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'YYYY')",
-        "2022",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'YY')",
-        "22",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'Month')",
-        "June",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'Mon')",
-        "Jun",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'MM')",
-        "06",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'CC')",
-        "21",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'DDD')",
-        "154",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'DD')",
-        "03",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'D')",
-        "6",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'W')",
-        "1",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'WW')",
-        "23",
-        "VARCHAR NOT NULL");
-    f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'gggggg')",
-        "gggggg",
-        "VARCHAR NOT NULL");
-    f.checkNull("to_char(timestamp '2022-06-03 12:15:48.678', NULL)");
-    f.checkNull("to_char(cast(NULL as timestamp), NULL)");
-    f.checkNull("to_char(cast(NULL as timestamp), 'Day')");
+    final SqlOperatorFixture f0 = 
fixture().setFor(SqlLibraryOperators.TO_CHAR);
+    final Consumer<SqlOperatorFixture> consumer = f -> {
+      f.checkString(
+          "to_char(timestamp '2022-06-03 12:15:48.678', 'YYYY-MM-DD 
HH24:MI:SS.MS TZ')",
+          "2022-06-03 12:15:48.678",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 12:15:48.678', 'Day')",
+          "Friday",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '0001-01-01 00:00:00.000', 'Day')",
+          "Monday",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 12:15:48.678', 'DY')",
+          "FRI",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '0001-01-01 00:00:00.000', 'DY')",
+          "MON",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 12:15:48.678', 'CC')",
+          "21",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'HH12')",
+          "01",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'HH24')",
+          "13",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'MI')",
+          "15",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'MS')",
+          "678",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'Q')",
+          "2",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'IW')",
+          "23",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'YYYY')",
+          "2022",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'YY')",
+          "22",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'Month')",
+          "June",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'Mon')",
+          "Jun",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'MM')",
+          "06",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'CC')",
+          "21",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'DDD')",
+          "154",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'DD')",
+          "03",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'D')",
+          "6",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'W')",
+          "1",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'WW')",
+          "23",
+          "VARCHAR NOT NULL");
+      f.checkString("to_char(timestamp '2022-06-03 13:15:48.678', 'gggggg')",
+          "gggggg",
+          "VARCHAR NOT NULL");
+      f.checkNull("to_char(timestamp '2022-06-03 12:15:48.678', NULL)");
+      f.checkNull("to_char(cast(NULL as timestamp), NULL)");
+      f.checkNull("to_char(cast(NULL as timestamp), 'Day')");
+    };
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.MYSQL, SqlLibrary.ORACLE, SqlLibrary.REDSHIFT);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testToCharPg() {
@@ -5066,69 +5075,77 @@ public class SqlOperatorTest {
   }
 
   @Test void testToDate() {
-    final SqlOperatorFixture f = fixture().withLibrary(SqlLibrary.POSTGRESQL);
-    f.setFor(SqlLibraryOperators.TO_DATE);
+    final SqlOperatorFixture f0 = 
fixture().setFor(SqlLibraryOperators.TO_DATE);
 
-    f.checkString("to_date('2022-06-03', 'YYYY-MM-DD')",
-        "2022-06-03",
-        "DATE NOT NULL");
-    f.checkString("to_date('0001-01-01', 'YYYY-MM-DD')",
-        "0001-01-01",
-        "DATE NOT NULL");
-    f.checkString("to_date('Jun 03, 2022', 'Mon DD, YYYY')",
-        "2022-06-03",
-        "DATE NOT NULL");
-    f.checkString("to_date('2022-June-03', 'YYYY-Month-DD')",
-        "2022-06-03",
-        "DATE NOT NULL");
-    f.checkString("to_date('2022-Jun-03', 'YYYY-Mon-DD')",
-        "2022-06-03",
-        "DATE NOT NULL");
-    f.checkString("to_date('2022-154', 'YYYY-DDD')",
-        "2022-06-03",
-        "DATE NOT NULL");
-    f.checkFails("to_date('ABCD', 'YYYY-MM-DD')",
-        "java.sql.SQLException: Invalid format: 'YYYY-MM-DD' for datetime 
string: 'ABCD'.",
-        true);
-    f.checkFails("to_date('2022-06-03', 'Invalid')",
-        "Illegal pattern character 'I'",
-        true);
-    f.checkNull("to_date(NULL, 'YYYY-MM-DD')");
-    f.checkNull("to_date('2022-06-03', NULL)");
-    f.checkNull("to_date(NULL, NULL)");
+    final Consumer<SqlOperatorFixture> consumer = f -> {
+      f.checkString("to_date('2022-06-03', 'YYYY-MM-DD')",
+          "2022-06-03",
+          "DATE NOT NULL");
+      f.checkString("to_date('0001-01-01', 'YYYY-MM-DD')",
+          "0001-01-01",
+          "DATE NOT NULL");
+      f.checkString("to_date('Jun 03, 2022', 'Mon DD, YYYY')",
+          "2022-06-03",
+          "DATE NOT NULL");
+      f.checkString("to_date('2022-June-03', 'YYYY-Month-DD')",
+          "2022-06-03",
+          "DATE NOT NULL");
+      f.checkString("to_date('2022-Jun-03', 'YYYY-Mon-DD')",
+          "2022-06-03",
+          "DATE NOT NULL");
+      f.checkString("to_date('2022-154', 'YYYY-DDD')",
+          "2022-06-03",
+          "DATE NOT NULL");
+      f.checkFails("to_date('ABCD', 'YYYY-MM-DD')",
+          "java.sql.SQLException: Invalid format: 'YYYY-MM-DD' for datetime 
string: 'ABCD'.",
+          true);
+      f.checkFails("to_date('2022-06-03', 'Invalid')",
+          "Illegal pattern character 'I'",
+          true);
+      f.checkNull("to_date(NULL, 'YYYY-MM-DD')");
+      f.checkNull("to_date('2022-06-03', NULL)");
+      f.checkNull("to_date(NULL, NULL)");
+    };
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.ORACLE, SqlLibrary.POSTGRESQL, SqlLibrary.REDSHIFT);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testToTimestamp() {
-    final SqlOperatorFixture f = fixture().withLibrary(SqlLibrary.POSTGRESQL);
-    f.setFor(SqlLibraryOperators.TO_TIMESTAMP);
+    final SqlOperatorFixture f0 = 
fixture().setFor(SqlLibraryOperators.TO_TIMESTAMP);
 
-    f.checkString("to_timestamp('2022-06-03 18:34:56', 'YYYY-MM-DD 
HH24:MI:SS')",
-        "2022-06-03 18:34:56",
-        "TIMESTAMP(0) NOT NULL");
-    f.checkString("to_timestamp('0001-01-01 18:43:56', 'YYYY-MM-DD 
HH24:MI:SS')",
-        "0001-01-01 18:43:56",
-        "TIMESTAMP(0) NOT NULL");
-    f.checkString("to_timestamp('18:34:56 Jun 03, 2022', 'HH24:MI:SS Mon DD, 
YYYY')",
-        "2022-06-03 18:34:56",
-        "TIMESTAMP(0) NOT NULL");
-    f.checkString("to_timestamp('18:34:56 2022-June-03', 'HH24:MI:SS 
YYYY-Month-DD')",
-        "2022-06-03 18:34:56",
-        "TIMESTAMP(0) NOT NULL");
-    f.checkString("to_timestamp('18:34:56 2022-Jun-03', 'HH24:MI:SS 
YYYY-Mon-DD')",
-        "2022-06-03 18:34:56",
-        "TIMESTAMP(0) NOT NULL");
-    f.checkString("to_timestamp('18:34:56 2022-154', 'HH24:MI:SS YYYY-DDD')",
-        "2022-06-03 18:34:56",
-        "TIMESTAMP(0) NOT NULL");
-    f.checkFails("to_timestamp('ABCD', 'YYYY-MM-DD HH24:MI:SS')",
-        "java.sql.SQLException: Invalid format: 'YYYY-MM-DD HH24:MI:SS' for 
datetime string: 'ABCD'.",
-        true);
-    f.checkFails("to_timestamp('2022-06-03 18:34:56', 'Invalid')",
-        "Illegal pattern character 'I'",
-        true);
-    f.checkNull("to_timestamp(NULL, 'YYYY-MM-DD HH24:MI:SS')");
-    f.checkNull("to_timestamp('2022-06-03 18:34:56', NULL)");
-    f.checkNull("to_timestamp(NULL, NULL)");
+    final Consumer<SqlOperatorFixture> consumer = f -> {
+      f.checkString("to_timestamp('2022-06-03 18:34:56', 'YYYY-MM-DD 
HH24:MI:SS')",
+          "2022-06-03 18:34:56",
+          "TIMESTAMP(0) NOT NULL");
+      f.checkString("to_timestamp('0001-01-01 18:43:56', 'YYYY-MM-DD 
HH24:MI:SS')",
+          "0001-01-01 18:43:56",
+          "TIMESTAMP(0) NOT NULL");
+      f.checkString("to_timestamp('18:34:56 Jun 03, 2022', 'HH24:MI:SS Mon DD, 
YYYY')",
+          "2022-06-03 18:34:56",
+          "TIMESTAMP(0) NOT NULL");
+      f.checkString("to_timestamp('18:34:56 2022-June-03', 'HH24:MI:SS 
YYYY-Month-DD')",
+          "2022-06-03 18:34:56",
+          "TIMESTAMP(0) NOT NULL");
+      f.checkString("to_timestamp('18:34:56 2022-Jun-03', 'HH24:MI:SS 
YYYY-Mon-DD')",
+          "2022-06-03 18:34:56",
+          "TIMESTAMP(0) NOT NULL");
+      f.checkString("to_timestamp('18:34:56 2022-154', 'HH24:MI:SS YYYY-DDD')",
+          "2022-06-03 18:34:56",
+          "TIMESTAMP(0) NOT NULL");
+      f.checkFails("to_timestamp('ABCD', 'YYYY-MM-DD HH24:MI:SS')",
+          "java.sql.SQLException: Invalid format: 'YYYY-MM-DD HH24:MI:SS' for 
datetime string: 'ABCD'.",
+          true);
+      f.checkFails("to_timestamp('2022-06-03 18:34:56', 'Invalid')",
+          "Illegal pattern character 'I'",
+          true);
+      f.checkNull("to_timestamp(NULL, 'YYYY-MM-DD HH24:MI:SS')");
+      f.checkNull("to_timestamp('2022-06-03 18:34:56', NULL)");
+      f.checkNull("to_timestamp(NULL, NULL)");
+    };
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.ORACLE, SqlLibrary.POSTGRESQL, SqlLibrary.REDSHIFT);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testFromBase64() {
@@ -5199,7 +5216,7 @@ public class SqlOperatorTest {
         false);
     final List<SqlLibrary> libraries =
         ImmutableList.of(SqlLibrary.BIG_QUERY, SqlLibrary.MYSQL,
-            SqlLibrary.POSTGRESQL, SqlLibrary.SPARK);
+            SqlLibrary.POSTGRESQL, SqlLibrary.REDSHIFT, SqlLibrary.SPARK);
     final Consumer<SqlOperatorFixture> consumer = f -> {
       f.checkString("md5(x'')",
           "d41d8cd98f00b204e9800998ecf8427e",
@@ -5224,7 +5241,7 @@ public class SqlOperatorTest {
         false);
     final List<SqlLibrary> libraries =
         ImmutableList.of(SqlLibrary.BIG_QUERY, SqlLibrary.MYSQL,
-            SqlLibrary.POSTGRESQL, SqlLibrary.SPARK);
+            SqlLibrary.POSTGRESQL, SqlLibrary.REDSHIFT, SqlLibrary.SPARK);
     final Consumer<SqlOperatorFixture> consumer = f -> {
       f.checkString("sha1(x'')",
           "da39a3ee5e6b4b0d3255bfef95601890afd80709",
@@ -5344,9 +5361,10 @@ public class SqlOperatorTest {
       f.checkNull("REPEAT('abc', cast(null as integer))");
       f.checkNull("REPEAT(cast(null as varchar(1)), cast(null as integer))");
     };
-    f0.forEachLibrary(
+    final List<SqlLibrary> libraries =
         list(SqlLibrary.BIG_QUERY, SqlLibrary.MYSQL,
-        SqlLibrary.POSTGRESQL, SqlLibrary.SPARK), consumer);
+            SqlLibrary.POSTGRESQL, SqlLibrary.SPARK);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testSpaceFunc() {
@@ -5380,8 +5398,8 @@ public class SqlOperatorTest {
         "No match found for function signature SOUNDEX\\(<CHARACTER>\\)",
         false);
     final List<SqlLibrary> libraries =
-        ImmutableList.of(SqlLibrary.BIG_QUERY, SqlLibrary.MYSQL,
-            SqlLibrary.ORACLE, SqlLibrary.POSTGRESQL);
+        list(SqlLibrary.BIG_QUERY, SqlLibrary.MYSQL,
+            SqlLibrary.ORACLE, SqlLibrary.POSTGRESQL, SqlLibrary.REDSHIFT);
     final Consumer<SqlOperatorFixture> consumer = f -> {
       f.checkString("SOUNDEX('TECH ON THE NET')", "T253", "VARCHAR(4) NOT 
NULL");
       f.checkString("SOUNDEX('Miller')", "M460", "VARCHAR(4) NOT NULL");
@@ -5422,19 +5440,23 @@ public class SqlOperatorTest {
   }
 
   @Test void testDifferenceFunc() {
-    final SqlOperatorFixture f = fixture()
-        .setFor(SqlLibraryOperators.DIFFERENCE)
-        .withLibrary(SqlLibrary.POSTGRESQL);
-    f.checkScalarExact("DIFFERENCE('Miller', 'miller')", 4);
-    f.checkScalarExact("DIFFERENCE('Miller', 'myller')", 4);
-    f.checkScalarExact("DIFFERENCE('muller', 'miller')", 4);
-    f.checkScalarExact("DIFFERENCE('muller', 'miller')", 4);
-    f.checkScalarExact("DIFFERENCE('muller', 'milk')", 2);
-    f.checkScalarExact("DIFFERENCE('muller', 'mile')", 2);
-    f.checkScalarExact("DIFFERENCE('muller', 'm')", 1);
-    f.checkScalarExact("DIFFERENCE('muller', 'lee')", 0);
-    f.checkNull("DIFFERENCE('muller', cast(null as varchar(1)))");
-    f.checkNull("DIFFERENCE(cast(null as varchar(1)), 'muller')");
+    final SqlOperatorFixture f0 = fixture()
+        .setFor(SqlLibraryOperators.DIFFERENCE);
+    final Consumer<SqlOperatorFixture> consumer = f -> {
+      f.checkScalarExact("DIFFERENCE('Miller', 'miller')", 4);
+      f.checkScalarExact("DIFFERENCE('Miller', 'myller')", 4);
+      f.checkScalarExact("DIFFERENCE('muller', 'miller')", 4);
+      f.checkScalarExact("DIFFERENCE('muller', 'miller')", 4);
+      f.checkScalarExact("DIFFERENCE('muller', 'milk')", 2);
+      f.checkScalarExact("DIFFERENCE('muller', 'mile')", 2);
+      f.checkScalarExact("DIFFERENCE('muller', 'm')", 1);
+      f.checkScalarExact("DIFFERENCE('muller', 'lee')", 0);
+      f.checkNull("DIFFERENCE('muller', cast(null as varchar(1)))");
+      f.checkNull("DIFFERENCE(cast(null as varchar(1)), 'muller')");
+    };
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.POSTGRESQL, SqlLibrary.REDSHIFT);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testReverseFunc() {
@@ -5555,9 +5577,10 @@ public class SqlOperatorTest {
       f.checkNull("left(cast(null as binary(1)), -2)");
       f.checkNull("left(x'ABCdef', cast(null as Integer))");
     };
-    f0.forEachLibrary(
+    final List<SqlLibrary> libraries =
         list(SqlLibrary.BIG_QUERY, SqlLibrary.MYSQL, SqlLibrary.POSTGRESQL,
-         SqlLibrary.SPARK), consumer);
+            SqlLibrary.REDSHIFT, SqlLibrary.SPARK);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testRightFunc() {
@@ -5581,9 +5604,10 @@ public class SqlOperatorTest {
       f.checkNull("right(x'ABCdef', cast(null as Integer))");
     };
 
-    f0.forEachLibrary(
+    final List<SqlLibrary> libraries =
         list(SqlLibrary.BIG_QUERY, SqlLibrary.MYSQL, SqlLibrary.POSTGRESQL,
-        SqlLibrary.SPARK), consumer);
+            SqlLibrary.REDSHIFT, SqlLibrary.SPARK);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testRegexpContainsFunc() {
@@ -5700,7 +5724,10 @@ public class SqlOperatorTest {
       f.checkQuery("select regexp_replace('a b c', 'b', 'X', 1, 3)");
       f.checkQuery("select regexp_replace('a b c', 'b', 'X', 1, 3, 'i')");
     };
-    f0.forEachLibrary(list(SqlLibrary.BIG_QUERY, SqlLibrary.MYSQL, 
SqlLibrary.ORACLE), consumer);
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.BIG_QUERY, SqlLibrary.MYSQL, SqlLibrary.ORACLE,
+            SqlLibrary.REDSHIFT);
+    f0.forEachLibrary(libraries, consumer);
 
     // Tests for double-backslash indexed capturing groups for regexp_replace 
in BQ
     final SqlOperatorFixture f1 =
@@ -7019,7 +7046,9 @@ public class SqlOperatorTest {
         fixture.checkScalarApprox("random()", "DOUBLE NOT NULL", isWithin(0.5, 
0.5));
       }
     };
-    f.forEachLibrary(list(SqlLibrary.POSTGRESQL), consumer);
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.POSTGRESQL, SqlLibrary.REDSHIFT);
+    f.forEachLibrary(libraries, consumer);
   }
 
   @Test void testRandIntegerSeedFunc() {
@@ -9876,7 +9905,10 @@ public class SqlOperatorTest {
       f.checkFails("lpad(x'aa', 3, x'')",
           "Third argument \\(pad pattern\\) for LPAD/RPAD must not be empty", 
true);
     };
-    f0.forEachLibrary(list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, 
SqlLibrary.SPARK), consumer);
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, SqlLibrary.POSTGRESQL,
+            SqlLibrary.REDSHIFT, SqlLibrary.SPARK);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testRpadFunction() {
@@ -9905,7 +9937,10 @@ public class SqlOperatorTest {
       f.checkFails("rpad(x'aa', 3, x'')",
           "Third argument \\(pad pattern\\) for LPAD/RPAD must not be empty", 
true);
     };
-    f0.forEachLibrary(list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, 
SqlLibrary.SPARK), consumer);
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, SqlLibrary.POSTGRESQL,
+            SqlLibrary.REDSHIFT, SqlLibrary.SPARK);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testContainsSubstrFunc() {
@@ -9973,24 +10008,28 @@ public class SqlOperatorTest {
     f0.checkFails("^strpos('abc', 'a')^",
         "No match found for function signature STRPOS\\(<CHARACTER>, 
<CHARACTER>\\)",
         false);
-    final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.BIG_QUERY);
-    f.checkScalar("STRPOS('abc', 'a')", "1", "INTEGER NOT NULL");
-    f.checkScalar("STRPOS('abcabc', 'bc')", "2", "INTEGER NOT NULL");
-    f.checkScalar("STRPOS('abcabc', 'd')", "0", "INTEGER NOT NULL");
-    f.checkScalar("STRPOS('abc', '')", "1", "INTEGER NOT NULL");
-    f.checkScalar("STRPOS('', 'a')", "0", "INTEGER NOT NULL");
-    f.checkNull("STRPOS(null, 'a')");
-    f.checkNull("STRPOS('a', null)");
-
-    // test for BINARY
-    f.checkScalar("STRPOS(x'2212', x'12')", "2", "INTEGER NOT NULL");
-    f.checkScalar("STRPOS(x'2122', x'12')", "0", "INTEGER NOT NULL");
-    f.checkScalar("STRPOS(x'1222', x'12')", "1", "INTEGER NOT NULL");
-    f.checkScalar("STRPOS(x'1111', x'22')", "0", "INTEGER NOT NULL");
-    f.checkScalar("STRPOS(x'2122', x'')", "1", "INTEGER NOT NULL");
-    f.checkScalar("STRPOS(x'', x'12')", "0", "INTEGER NOT NULL");
-    f.checkNull("STRPOS(null, x'')");
-    f.checkNull("STRPOS(x'', null)");
+    final Consumer<SqlOperatorFixture> consumer = f -> {
+      f.checkScalar("STRPOS('abc', 'a')", "1", "INTEGER NOT NULL");
+      f.checkScalar("STRPOS('abcabc', 'bc')", "2", "INTEGER NOT NULL");
+      f.checkScalar("STRPOS('abcabc', 'd')", "0", "INTEGER NOT NULL");
+      f.checkScalar("STRPOS('abc', '')", "1", "INTEGER NOT NULL");
+      f.checkScalar("STRPOS('', 'a')", "0", "INTEGER NOT NULL");
+      f.checkNull("STRPOS(null, 'a')");
+      f.checkNull("STRPOS('a', null)");
+
+      // test for BINARY
+      f.checkScalar("STRPOS(x'2212', x'12')", "2", "INTEGER NOT NULL");
+      f.checkScalar("STRPOS(x'2122', x'12')", "0", "INTEGER NOT NULL");
+      f.checkScalar("STRPOS(x'1222', x'12')", "1", "INTEGER NOT NULL");
+      f.checkScalar("STRPOS(x'1111', x'22')", "0", "INTEGER NOT NULL");
+      f.checkScalar("STRPOS(x'2122', x'')", "1", "INTEGER NOT NULL");
+      f.checkScalar("STRPOS(x'', x'12')", "0", "INTEGER NOT NULL");
+      f.checkNull("STRPOS(null, x'')");
+      f.checkNull("STRPOS(x'', null)");
+    };
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.BIG_QUERY, SqlLibrary.POSTGRESQL, SqlLibrary.REDSHIFT);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testInstrFunction() {
@@ -10021,7 +10060,9 @@ public class SqlOperatorTest {
       f.checkNull("INSTR(null, x'', 1, 1)");
       f.checkNull("INSTR(x'', null, 1, 1)");
     };
-    f0.forEachLibrary(list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE), consumer);
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.BIG_QUERY, SqlLibrary.MYSQL, SqlLibrary.ORACLE);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testSnowflakeStartsWithFunc() {
@@ -10625,7 +10666,10 @@ public class SqlOperatorTest {
       f.checkString("rtrim(' aAa  ')", " aAa", "VARCHAR(6) NOT NULL");
       f.checkNull("rtrim(CAST(NULL AS VARCHAR(6)))");
     };
-    f0.forEachLibrary(list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, 
SqlLibrary.SPARK), consumer);
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, SqlLibrary.POSTGRESQL,
+            SqlLibrary.REDSHIFT, SqlLibrary.SPARK);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testLtrimFunc() {
@@ -10638,7 +10682,10 @@ public class SqlOperatorTest {
       f.checkString("ltrim(' aAa  ')", "aAa  ", "VARCHAR(6) NOT NULL");
       f.checkNull("ltrim(CAST(NULL AS VARCHAR(6)))");
     };
-    f0.forEachLibrary(list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, 
SqlLibrary.SPARK), consumer);
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, SqlLibrary.POSTGRESQL,
+            SqlLibrary.REDSHIFT, SqlLibrary.SPARK);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testGreatestFunc() {
@@ -10660,7 +10707,10 @@ public class SqlOperatorTest {
       f12.checkString("greatest('show', 'on', 'earth')", "show",
           "VARCHAR(5) NOT NULL");
     };
-    f0.forEachLibrary(list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, 
SqlLibrary.SPARK), consumer);
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, SqlLibrary.REDSHIFT,
+            SqlLibrary.SPARK);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testLeastFunc() {
@@ -10682,7 +10732,10 @@ public class SqlOperatorTest {
       f12.checkString("least('show', 'on', 'earth')", "earth",
           "VARCHAR(5) NOT NULL");
     };
-    f0.forEachLibrary(list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, 
SqlLibrary.SPARK), consumer);
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.BIG_QUERY, SqlLibrary.ORACLE, SqlLibrary.REDSHIFT,
+            SqlLibrary.SPARK);
+    f0.forEachLibrary(libraries, consumer);
   }
 
   @Test void testIfNullFunc() {
@@ -10744,8 +10797,9 @@ public class SqlOperatorTest {
       f12.checkFails("^NVL2(2.0, 1, true)^", "Parameters must be of the same 
type", false);
       f12.checkFails("^NVL2(NULL, 1, true)^", "Parameters must be of the same 
type", false);
     };
-    f.forEachLibrary(list(SqlLibrary.ORACLE, SqlLibrary.SPARK), consumer);
-
+    final List<SqlLibrary> libraries =
+        list(SqlLibrary.ORACLE, SqlLibrary.REDSHIFT, SqlLibrary.SPARK);
+    f.forEachLibrary(libraries, consumer);
   }
 
   /** Tests the {@code NVL} and {@code IFNULL} operators. */
@@ -10771,6 +10825,7 @@ public class SqlOperatorTest {
 
   @Test void testDecodeFunc() {
     checkDecodeFunc(fixture().withLibrary(SqlLibrary.ORACLE));
+    checkDecodeFunc(fixture().withLibrary(SqlLibrary.REDSHIFT));
     checkDecodeFunc(fixture().withLibrary(SqlLibrary.SPARK));
   }
 
@@ -14502,6 +14557,7 @@ public class SqlOperatorTest {
         "No match found for function signature BOOLAND_AGG\\(<BOOLEAN>\\)", 
false);
 
     checkBoolAndFunc(f.withLibrary(SqlLibrary.POSTGRESQL));
+    checkBoolAndFunc(f.withLibrary(SqlLibrary.REDSHIFT));
     checkBoolAndFunc(f.withLibrary(SqlLibrary.SPARK));
     checkBoolAndAggFunc(f.withLibrary(SqlLibrary.SNOWFLAKE));
   }
@@ -14579,6 +14635,7 @@ public class SqlOperatorTest {
         "No match found for function signature BOOLOR_AGG\\(<BOOLEAN>\\)", 
false);
 
     checkBoolOrFunc(f.withLibrary(SqlLibrary.POSTGRESQL));
+    checkBoolOrFunc(f.withLibrary(SqlLibrary.REDSHIFT));
     checkBoolOrFunc(f.withLibrary(SqlLibrary.SPARK));
     checkBoolOrAggFunc(f.withLibrary(SqlLibrary.SNOWFLAKE));
   }

Reply via email to