Skip to content

Commit

Permalink
[CALCITE-6444] Add a function library for Amazon Redshift
Browse files Browse the repository at this point in the history
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#3829
  • Loading branch information
normanj-bitquill authored and julianhyde committed Jun 29, 2024
1 parent d74b283 commit cdb6522
Show file tree
Hide file tree
Showing 8 changed files with 399 additions and 267 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public static void main(String[] args) throws Exception {
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,
Expand Down
15 changes: 15 additions & 0 deletions core/src/main/java/org/apache/calcite/sql/fun/LibraryOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,19 @@
/** 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 {};
}
50 changes: 45 additions & 5 deletions core/src/main/java/org/apache/calcite/sql/fun/SqlLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@

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;
Expand All @@ -31,6 +33,9 @@

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;

/**
Expand Down Expand Up @@ -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. */
Expand All @@ -81,16 +90,28 @@ 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;

/** Name of this library when it appears in the connect string;
* 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("_", "")));
}

Expand All @@ -99,12 +120,21 @@ public List<SqlLibrary> children() {
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. */
Expand Down Expand Up @@ -167,10 +197,20 @@ private static void addParent(Set<SqlLibrary> set, SqlLibrary library) {
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
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;

Expand Down Expand Up @@ -141,6 +142,23 @@ private static boolean operatorIsInLibrary(String operatorName, Field field,
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;
}
Expand Down
Loading

0 comments on commit cdb6522

Please sign in to comment.