All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.org.apache.calcite.schema.Schemas Maven / Gradle / Ivy

There is a newer version: 5.4.0
Show newest version
/*
 * 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 com.hazelcast.org.apache.calcite.schema;

import com.hazelcast.org.apache.calcite.DataContext;
import com.hazelcast.org.apache.calcite.DataContexts;
import com.hazelcast.org.apache.calcite.adapter.enumerable.EnumUtils;
import com.hazelcast.org.apache.calcite.adapter.java.JavaTypeFactory;
import com.hazelcast.org.apache.calcite.config.CalciteConnectionConfig;
import com.hazelcast.org.apache.calcite.config.CalciteConnectionConfigImpl;
import com.hazelcast.org.apache.calcite.config.CalciteConnectionProperty;
import com.hazelcast.org.apache.calcite.jdbc.CalciteConnection;
import com.hazelcast.org.apache.calcite.jdbc.CalcitePrepare;
import com.hazelcast.org.apache.calcite.jdbc.CalciteSchema;
import com.hazelcast.org.apache.calcite.linq4j.Enumerable;
import com.hazelcast.org.apache.calcite.linq4j.QueryProvider;
import com.hazelcast.org.apache.calcite.linq4j.Queryable;
import com.hazelcast.org.apache.calcite.linq4j.tree.Expression;
import com.hazelcast.org.apache.calcite.linq4j.tree.Expressions;
import com.hazelcast.org.apache.calcite.linq4j.tree.MethodCallExpression;
import com.hazelcast.org.apache.calcite.materialize.Lattice;
import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeFactory;
import com.hazelcast.org.apache.calcite.rel.type.RelProtoDataType;
import com.hazelcast.org.apache.calcite.sql.type.SqlTypeUtil;
import com.hazelcast.org.apache.calcite.tools.RelRunner;
import com.hazelcast.org.apache.calcite.util.BuiltInMethod;
import com.hazelcast.org.apache.calcite.util.Pair;
import com.hazelcast.org.apache.calcite.util.Util;

import com.hazelcast.com.google.common.base.Preconditions;
import com.hazelcast.com.google.common.collect.ImmutableList;
import com.hazelcast.com.google.common.collect.ImmutableMap;
import com.hazelcast.com.google.common.collect.Lists;

import com.hazelcast.org.checkerframework.checker.nullness.qual.Nullable;

import java.lang.reflect.Type;
import java.sql.Connection;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import static com.hazelcast.org.apache.calcite.jdbc.CalciteSchema.LatticeEntry;

import static java.util.Objects.requireNonNull;

/**
 * Utility functions for schemas.
 */
public final class Schemas {

  private Schemas() {
    throw new AssertionError("no instances!");
  }

  public static CalciteSchema.@Nullable FunctionEntry resolve(
      RelDataTypeFactory typeFactory,
      String name,
      Collection functionEntries,
      List argumentTypes) {
    final List matches = new ArrayList<>();
    for (CalciteSchema.FunctionEntry entry : functionEntries) {
      if (matches(typeFactory, entry.getFunction(), argumentTypes)) {
        matches.add(entry);
      }
    }
    switch (matches.size()) {
    case 0:
      return null;
    case 1:
      return matches.get(0);
    default:
      throw new RuntimeException("More than one match for " + name
          + " with arguments " + argumentTypes);
    }
  }

  private static boolean matches(RelDataTypeFactory typeFactory,
      Function member, List argumentTypes) {
    List parameters = member.getParameters();
    if (parameters.size() != argumentTypes.size()) {
      return false;
    }
    for (int i = 0; i < argumentTypes.size(); i++) {
      RelDataType argumentType = argumentTypes.get(i);
      FunctionParameter parameter = parameters.get(i);
      if (!canConvert(argumentType, parameter.getType(typeFactory))) {
        return false;
      }
    }
    return true;
  }

  private static boolean canConvert(RelDataType fromType, RelDataType toType) {
    return SqlTypeUtil.canAssignFrom(toType, fromType);
  }

  /** Returns the expression for a schema. */
  public static Expression expression(SchemaPlus schema) {
    return schema.getExpression(schema.getParentSchema(), schema.getName());
  }

  /** Returns the expression for a sub-schema. */
  public static Expression subSchemaExpression(SchemaPlus schema, String name,
      Class type) {
    // (Type) schemaExpression.getSubSchema("name")
    final Expression schemaExpression = expression(schema);
    Expression call =
        Expressions.call(
            schemaExpression,
            BuiltInMethod.SCHEMA_GET_SUB_SCHEMA.method,
            Expressions.constant(name));
    //CHECKSTYLE: IGNORE 2
    //noinspection unchecked
    if (false && type != null && !type.isAssignableFrom(Schema.class)) {
      return unwrap(call, type);
    }
    return call;
  }

  /** Converts a schema expression to a given type by calling the
   * {@link SchemaPlus#unwrap(Class)} method. */
  public static Expression unwrap(Expression call, Class type) {
    return Expressions.convert_(
        Expressions.call(call, BuiltInMethod.SCHEMA_PLUS_UNWRAP.method,
            Expressions.constant(type)),
        type);
  }

  /** Returns the expression to access a table within a schema. */
  public static Expression tableExpression(SchemaPlus schema, Type elementType,
      String tableName, Class clazz) {
    final MethodCallExpression expression;
    if (Table.class.isAssignableFrom(clazz)) {
      expression = Expressions.call(
          expression(schema),
          BuiltInMethod.SCHEMA_GET_TABLE.method,
          Expressions.constant(tableName));
      if (ScannableTable.class.isAssignableFrom(clazz)) {
        return Expressions.call(
            BuiltInMethod.SCHEMAS_ENUMERABLE_SCANNABLE.method,
            Expressions.convert_(expression, ScannableTable.class),
            DataContext.ROOT);
      }
      if (FilterableTable.class.isAssignableFrom(clazz)) {
        return Expressions.call(
            BuiltInMethod.SCHEMAS_ENUMERABLE_FILTERABLE.method,
            Expressions.convert_(expression, FilterableTable.class),
            DataContext.ROOT);
      }
      if (ProjectableFilterableTable.class.isAssignableFrom(clazz)) {
        return Expressions.call(
            BuiltInMethod.SCHEMAS_ENUMERABLE_PROJECTABLE_FILTERABLE.method,
            Expressions.convert_(expression, ProjectableFilterableTable.class),
            DataContext.ROOT);
      }
    } else {
      expression = Expressions.call(
          BuiltInMethod.SCHEMAS_QUERYABLE.method,
          DataContext.ROOT,
          expression(schema),
          Expressions.constant(elementType),
          Expressions.constant(tableName));
    }
    return EnumUtils.convert(expression, clazz);
  }

  /**
   * Generates an expression with which table can be referenced in
   * generated code.
   *
   * @param schema    Schema
   * @param tableName Table name (unique within schema)
   * @param table     Table to be referenced
   * @param clazz     Class that provides specific methods for accessing table data.
   *                  It may differ from the {@code table} class; for example {@code clazz} may be
   *                  {@code MongoTable.MongoQueryable}, though {@code table} is {@code MongoTable}
   */
  public static Expression getTableExpression(SchemaPlus schema, String tableName,
      Table table, Class clazz) {
    if (table instanceof QueryableTable) {
      QueryableTable queryableTable = (QueryableTable) table;
      return queryableTable.getExpression(schema, tableName, clazz);
    } else if (table instanceof ScannableTable
        || table instanceof FilterableTable
        || table instanceof ProjectableFilterableTable) {
      return tableExpression(schema, Object[].class, tableName,
          table.getClass());
    } else if (table instanceof StreamableTable) {
      return getTableExpression(schema, tableName,
          ((StreamableTable) table).stream(), clazz);
    } else {
      throw new UnsupportedOperationException();
    }
  }

  public static DataContext createDataContext(
      Connection connection, @Nullable SchemaPlus rootSchema) {
    return DataContexts.of((CalciteConnection) connection, rootSchema);
  }

  /** Returns a {@link Queryable}, given a fully-qualified table name. */
  public static  Queryable queryable(DataContext root, Class clazz,
      String... names) {
    return queryable(root, clazz, Arrays.asList(names));
  }

  /** Returns a {@link Queryable}, given a fully-qualified table name as an
   * iterable. */
  public static  Queryable queryable(DataContext root, Class clazz,
      Iterable names) {
    SchemaPlus schema = root.getRootSchema();
    for (Iterator iterator = names.iterator();;) {
      String name = iterator.next();
      requireNonNull(schema, "schema");
      if (iterator.hasNext()) {
        SchemaPlus next = schema.getSubSchema(name);
        if (next == null) {
          throw new IllegalArgumentException("schema " + name + " is not found in " + schema);
        }
        schema = next;
      } else {
        return queryable(root, schema, clazz, name);
      }
    }
  }

  /** Returns a {@link Queryable}, given a schema and table name. */
  public static  Queryable queryable(DataContext root, SchemaPlus schema,
      Class clazz, String tableName) {
    QueryableTable table = (QueryableTable) requireNonNull(
        schema.getTable(tableName),
        () -> "table " + tableName + " is not found in " + schema);
    QueryProvider queryProvider = root.getQueryProvider();
    return table.asQueryable(queryProvider, schema, tableName);
  }

  /** Returns an {@link com.hazelcast.org.apache.calcite.linq4j.Enumerable} over the rows of
   * a given table, representing each row as an object array. */
  public static Enumerable<@Nullable Object[]> enumerable(final ScannableTable table,
      final DataContext root) {
    return table.scan(root);
  }

  /** Returns an {@link com.hazelcast.org.apache.calcite.linq4j.Enumerable} over the rows of
   * a given table, not applying any filters, representing each row as an object
   * array. */
  public static Enumerable<@Nullable Object[]> enumerable(final FilterableTable table,
      final DataContext root) {
    return table.scan(root, new ArrayList<>());
  }

  /** Returns an {@link com.hazelcast.org.apache.calcite.linq4j.Enumerable} over the rows of
   * a given table, not applying any filters and projecting all columns,
   * representing each row as an object array. */
  public static Enumerable<@Nullable Object[]> enumerable(
      final ProjectableFilterableTable table, final DataContext root) {
    JavaTypeFactory typeFactory = root.getTypeFactory();
    return table.scan(root, new ArrayList<>(),
        identity(table.getRowType(typeFactory).getFieldCount()));
  }

  private static int[] identity(int count) {
    final int[] integers = new int[count];
    for (int i = 0; i < integers.length; i++) {
      integers[i] = i;
    }
    return integers;
  }

  /** Returns an {@link com.hazelcast.org.apache.calcite.linq4j.Enumerable} over object
   * arrays, given a fully-qualified table name which leads to a
   * {@link ScannableTable}. */
  public static @Nullable Table table(DataContext root, String... names) {
    SchemaPlus schema = root.getRootSchema();
    final List nameList = Arrays.asList(names);
    for (Iterator iterator = nameList.iterator();;) {
      String name = iterator.next();
      requireNonNull(schema, "schema");
      if (iterator.hasNext()) {
        SchemaPlus next = schema.getSubSchema(name);
        if (next == null) {
          throw new IllegalArgumentException("schema " + name + " is not found in " + schema);
        }
        schema = next;
      } else {
        return schema.getTable(name);
      }
    }
  }

  /** Parses and validates a SQL query. For use within Calcite only. */
  public static CalcitePrepare.ParseResult parse(
      final CalciteConnection connection, final CalciteSchema schema,
      final @Nullable List schemaPath, final String sql) {
    final CalcitePrepare prepare = CalcitePrepare.DEFAULT_FACTORY.apply();
    final ImmutableMap propValues =
        ImmutableMap.of();
    final CalcitePrepare.Context context =
        makeContext(connection, schema, schemaPath, null, propValues);
    CalcitePrepare.Dummy.push(context);
    try {
      return prepare.parse(context, sql);
    } finally {
      CalcitePrepare.Dummy.pop(context);
    }
  }

  /** Parses and validates a SQL query and converts to relational algebra. For
   * use within Calcite only. */
  public static CalcitePrepare.ConvertResult convert(
      final CalciteConnection connection, final CalciteSchema schema,
      final List schemaPath, final String sql) {
    final CalcitePrepare prepare = CalcitePrepare.DEFAULT_FACTORY.apply();
    final ImmutableMap propValues =
        ImmutableMap.of();
    final CalcitePrepare.Context context =
        makeContext(connection, schema, schemaPath, null, propValues);
    CalcitePrepare.Dummy.push(context);
    try {
      return prepare.convert(context, sql);
    } finally {
      CalcitePrepare.Dummy.pop(context);
    }
  }

  /** Analyzes a view. For use within Calcite only. */
  public static CalcitePrepare.AnalyzeViewResult analyzeView(
      final CalciteConnection connection, final CalciteSchema schema,
      final @Nullable List schemaPath, final String viewSql,
      @Nullable List viewPath, boolean fail) {
    final CalcitePrepare prepare = CalcitePrepare.DEFAULT_FACTORY.apply();
    final ImmutableMap propValues =
        ImmutableMap.of();
    final CalcitePrepare.Context context =
        makeContext(connection, schema, schemaPath, viewPath, propValues);
    CalcitePrepare.Dummy.push(context);
    try {
      return prepare.analyzeView(context, viewSql, fail);
    } finally {
      CalcitePrepare.Dummy.pop(context);
    }
  }

  /** Prepares a SQL query for execution. For use within Calcite only. */
  public static CalcitePrepare.CalciteSignature prepare(
      final CalciteConnection connection, final CalciteSchema schema,
      final @Nullable List schemaPath, final String sql,
      final ImmutableMap map) {
    final CalcitePrepare prepare = CalcitePrepare.DEFAULT_FACTORY.apply();
    final CalcitePrepare.Context context =
        makeContext(connection, schema, schemaPath, null, map);
    CalcitePrepare.Dummy.push(context);
    try {
      return prepare.prepareSql(context, CalcitePrepare.Query.of(sql),
          Object[].class, -1);
    } finally {
      CalcitePrepare.Dummy.pop(context);
    }
  }

  /**
   * Creates a context for the purposes of preparing a statement.
   *
   * @param connection Connection
   * @param schema Schema
   * @param schemaPath Path wherein to look for functions
   * @param objectPath Path of the object being analyzed (usually a view),
   *                  or null
   * @param propValues Connection properties
   * @return Context
   */
  private static CalcitePrepare.Context makeContext(
      CalciteConnection connection, CalciteSchema schema,
      @Nullable List schemaPath, @Nullable List objectPath,
      final ImmutableMap propValues) {
    if (connection == null) {
      final CalcitePrepare.Context context0 = CalcitePrepare.Dummy.peek();
      final CalciteConnectionConfig config =
          mutate(context0.config(), propValues);
      return makeContext(config, context0.getTypeFactory(),
          context0.getDataContext(), schema, schemaPath, objectPath);
    } else {
      final CalciteConnectionConfig config =
          mutate(connection.config(), propValues);
      return makeContext(config, connection.getTypeFactory(),
          DataContexts.of(connection, schema.root().plus()), schema,
          schemaPath, objectPath);
    }
  }

  private static CalciteConnectionConfig mutate(CalciteConnectionConfig config,
      ImmutableMap propValues) {
    for (Map.Entry e
        : propValues.entrySet()) {
      config =
          ((CalciteConnectionConfigImpl) config).set(e.getKey(), e.getValue());
    }
    return config;
  }

  private static CalcitePrepare.Context makeContext(
      final CalciteConnectionConfig connectionConfig,
      final JavaTypeFactory typeFactory,
      final DataContext dataContext,
      final CalciteSchema schema,
      final @Nullable List schemaPath, final @Nullable List objectPath_) {
    final @Nullable ImmutableList objectPath =
        objectPath_ == null ? null : ImmutableList.copyOf(objectPath_);
    return new CalcitePrepare.Context() {
      @Override public JavaTypeFactory getTypeFactory() {
        return typeFactory;
      }

      @Override public CalciteSchema getRootSchema() {
        return schema.root();
      }

      @Override public CalciteSchema getMutableRootSchema() {
        return getRootSchema();
      }

      @Override public List getDefaultSchemaPath() {
        // schemaPath is usually null. If specified, it overrides schema
        // as the context within which the SQL is validated.
        if (schemaPath == null) {
          return schema.path(null);
        }
        return schemaPath;
      }

      @Override public @Nullable List getObjectPath() {
        return objectPath;
      }

      @Override public CalciteConnectionConfig config() {
        return connectionConfig;
      }

      @Override public DataContext getDataContext() {
        return dataContext;
      }

      @Override public RelRunner getRelRunner() {
        throw new UnsupportedOperationException();
      }

      @Override public CalcitePrepare.SparkHandler spark() {
        final boolean enable = config().spark();
        return CalcitePrepare.Dummy.getSparkHandler(enable);
      }
    };
  }

  /** Returns an implementation of
   * {@link RelProtoDataType}
   * that asks a given table for its row type with a given type factory. */
  public static RelProtoDataType proto(final Table table) {
    return table::getRowType;
  }

  /** Returns an implementation of {@link RelProtoDataType}
   * that asks a given scalar function for its return type with a given type
   * factory. */
  public static RelProtoDataType proto(final ScalarFunction function) {
    return function::getReturnType;
  }

  /** Returns the star tables defined in a schema.
   *
   * @param schema Schema */
  public static List getStarTables(
      CalciteSchema schema) {
    final List list = getLatticeEntries(schema);
    return Util.transform(list, entry -> {
      final CalciteSchema.TableEntry starTable =
          requireNonNull(entry, "entry").getStarTable();
      assert starTable.getTable().getJdbcTableType()
          == Schema.TableType.STAR;
      return entry.getStarTable();
    });
  }

  /** Returns the lattices defined in a schema.
   *
   * @param schema Schema */
  public static List getLattices(CalciteSchema schema) {
    final List list = getLatticeEntries(schema);
    return Util.transform(list, LatticeEntry::getLattice);
  }

  /** Returns the lattices defined in a schema.
   *
   * @param schema Schema */
  public static List getLatticeEntries(
      CalciteSchema schema) {
    final List list = new ArrayList<>();
    gatherLattices(schema, list);
    return list;
  }

  private static void gatherLattices(CalciteSchema schema,
      List list) {
    list.addAll(schema.getLatticeMap().values());
    for (CalciteSchema subSchema : schema.getSubSchemaMap().values()) {
      gatherLattices(subSchema, list);
    }
  }

  /** Returns a sub-schema of a given schema obtained by following a sequence
   * of names.
   *
   * 

The result is null if the initial schema is null or any sub-schema does * not exist. */ public static @Nullable CalciteSchema subSchema(CalciteSchema schema, Iterable names) { @Nullable CalciteSchema current = schema; for (String string : names) { if (current == null) { return null; } current = current.getSubSchema(string, false); } return current; } /** Generates a table name that is unique within the given schema. */ public static String uniqueTableName(CalciteSchema schema, String base) { String t = requireNonNull(base, "base"); for (int x = 0; schema.getTable(t, true) != null; x++) { t = base + x; } return t; } /** Creates a path with a given list of names starting from a given root * schema. */ public static Path path(CalciteSchema rootSchema, Iterable names) { final ImmutableList.Builder> builder = ImmutableList.builder(); Schema schema = rootSchema.plus(); final Iterator iterator = names.iterator(); if (!iterator.hasNext()) { return PathImpl.EMPTY; } if (!rootSchema.name.isEmpty()) { // If path starts with the name of the root schema, ignore the first step // in the path. Preconditions.checkState(rootSchema.name.equals(iterator.next())); } for (;;) { final String name = iterator.next(); builder.add(Pair.of(name, schema)); if (!iterator.hasNext()) { return path(builder.build()); } Schema next = schema.getSubSchema(name); if (next == null) { throw new IllegalArgumentException("schema " + name + " is not found in " + schema); } schema = next; } } public static PathImpl path(ImmutableList> build) { return new PathImpl(build); } /** Returns the path to get to a schema from its root. */ public static Path path(SchemaPlus schema) { List> list = new ArrayList<>(); for (SchemaPlus s = schema; s != null; s = s.getParentSchema()) { list.add(Pair.of(s.getName(), s)); } return new PathImpl(ImmutableList.copyOf(Lists.reverse(list))); } /** Implementation of {@link Path}. */ private static class PathImpl extends AbstractList> implements Path { private final ImmutableList> pairs; private static final PathImpl EMPTY = new PathImpl(ImmutableList.of()); PathImpl(ImmutableList> pairs) { this.pairs = pairs; } @Override public boolean equals(@Nullable Object o) { return this == o || o instanceof PathImpl && pairs.equals(((PathImpl) o).pairs); } @Override public int hashCode() { return pairs.hashCode(); } @Override public Pair get(int index) { return pairs.get(index); } @Override public int size() { return pairs.size(); } @Override public Path parent() { if (pairs.isEmpty()) { throw new IllegalArgumentException("at root"); } return new PathImpl(pairs.subList(0, pairs.size() - 1)); } @Override public List names() { return new AbstractList() { @Override public String get(int index) { return pairs.get(index + 1).left; } @Override public int size() { return pairs.size() - 1; } }; } @Override public List schemas() { return Pair.right(pairs); } } }