SqlLibrary.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.calcite.sql.fun;

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;
import java.util.Map;
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;

/**
 * A library is a collection of SQL functions and operators.
 *
 * <p>Typically, such collections are associated with a particular dialect or
 * database. For example, {@link SqlLibrary#ORACLE} is a collection of functions
 * that are in the Oracle database but not the SQL standard.
 *
 * <p>In {@link SqlLibraryOperatorTableFactory} this annotation is applied to
 * function definitions to include them in a particular library. It allows
 * an operator to belong to more than one library.
 *
 * @see LibraryOperator
 */
public enum SqlLibrary {
  /** The standard operators. */
  STANDARD("", "standard"),
  /** Geospatial operators. */
  SPATIAL("s", "spatial"),
  /** A collection of operators that could be used in all libraries;
   * does not include STANDARD and SPATIAL. */
  ALL("*", "all"),
  /** A collection of operators that are in Google BigQuery but not in standard
   * SQL. */
  BIG_QUERY("b", "bigquery"),
  /** Calcite-specific extensions. */
  CALCITE("c", "calcite"),
  /** A collection of operators that are in Apache Hive but not in standard
   * SQL. */
  HIVE("h", "hive"),
  /** A collection of operators that are in Microsoft SQL Server (MSSql) but not
   * in standard SQL. */
  MSSQL("q", "mssql"),
  /** A collection of operators that are in MySQL but not in standard SQL. */
  MYSQL("m", "mysql"),
  /** A collection of operators that are in Oracle but not in standard SQL. */
  ORACLE("o", "oracle"),
  /** A collection of operators that are in PostgreSQL but not in standard
   * SQL. */
  POSTGRESQL("p", "postgresql"),
  /** 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. */
  SPARK("s", "spark"),
  /** A collection of operators that are in ClickHouse but not in standard
   * SQL. */
  CLICKHOUSE("i", "clickhouse");

  /** 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("_", "")));
  }

  @SuppressWarnings("SwitchStatementWithTooFewBranches")
  public List<SqlLibrary> children() {
    switch (this) {
    case ALL:
      return ImmutableList.of(BIG_QUERY, CALCITE, HIVE, MSSQL, MYSQL, ORACLE,
          POSTGRESQL, REDSHIFT, SNOWFLAKE, SPARK, CLICKHOUSE);
    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. */
  public static @Nullable SqlLibrary of(String name) {
    return MAP.get(name);
  }

  /** Parses a comma-separated string such as "standard,oracle". */
  public static List<SqlLibrary> parse(String libraryNameList) {
    final ImmutableList.Builder<SqlLibrary> list = ImmutableList.builder();
    if (!libraryNameList.isEmpty()) {
      for (String libraryName : libraryNameList.split(",")) {
        @Nullable SqlLibrary library = SqlLibrary.of(libraryName);
        if (library == null) {
          throw new IllegalArgumentException("unknown library '" + libraryName
              + "'");
        }
        list.add(library);
      }
    }
    return list.build();
  }

  /** Expands libraries in place.
   *
   * <p>Preserves order, and ensures that no library occurs more than once. */
  public static List<SqlLibrary> expand(
      Iterable<? extends SqlLibrary> libraries) {
    // LinkedHashSet ensures that libraries are added only once, and order is
    // preserved.
    final Set<SqlLibrary> set = new LinkedHashSet<>();
    libraries.forEach(library -> addExpansion(set, library));
    return ImmutableList.copyOf(set);
  }

  private static void addExpansion(Set<SqlLibrary> set, SqlLibrary library) {
    if (set.add(library)) {
      library.children().forEach(subLibrary -> addExpansion(set, subLibrary));
    }
  }

  /** Expands libraries in place. If any library is a child of 'all', ensures
   * that 'all' is in the list. */
  public static List<SqlLibrary> expandUp(
      Iterable<? extends SqlLibrary> libraries) {
    // LinkedHashSet ensures that libraries are added only once, and order is
    // preserved.
    final Set<SqlLibrary> set = new LinkedHashSet<>();
    libraries.forEach(library -> addParent(set, library));
    return ImmutableList.copyOf(set);
  }

  private static void addParent(Set<SqlLibrary> set, SqlLibrary library) {
    if (ALL.children().contains(library)) {
      set.add(ALL);
    }
    set.add(library);
  }

  static {
    final ImmutableMap.Builder<String, SqlLibrary> builder =
        ImmutableMap.builder();
    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();
  }
}