blob: 29a6f07c014576292f3503bb8effbbe54eb21899 [file] [log] [blame]
/*
* 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.prepare;
import org.apache.calcite.jdbc.CalciteSchema;
import org.apache.calcite.linq4j.tree.Expression;
import org.apache.calcite.linq4j.tree.TableExpressionFactory;
import org.apache.calcite.materialize.Lattice;
import org.apache.calcite.plan.RelOptSchema;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.RelDistribution;
import org.apache.calcite.rel.RelDistributionTraitDef;
import org.apache.calcite.rel.RelFieldCollation;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelReferentialConstraint;
import org.apache.calcite.rel.logical.LogicalTableScan;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rel.type.RelProtoDataType;
import org.apache.calcite.rel.type.RelRecordType;
import org.apache.calcite.schema.ColumnStrategy;
import org.apache.calcite.schema.ModifiableTable;
import org.apache.calcite.schema.Path;
import org.apache.calcite.schema.ScannableTable;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.SchemaVersion;
import org.apache.calcite.schema.Schemas;
import org.apache.calcite.schema.StreamableTable;
import org.apache.calcite.schema.Table;
import org.apache.calcite.schema.TemporalTable;
import org.apache.calcite.schema.TranslatableTable;
import org.apache.calcite.schema.Wrapper;
import org.apache.calcite.sql.SqlAccessType;
import org.apache.calcite.sql.validate.SqlModality;
import org.apache.calcite.sql.validate.SqlMonotonicity;
import org.apache.calcite.sql2rel.InitializerExpressionFactory;
import org.apache.calcite.sql2rel.NullInitializerExpressionFactory;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
import com.google.common.collect.ImmutableList;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.AbstractList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static java.util.Objects.requireNonNull;
/**
* Implementation of {@link org.apache.calcite.plan.RelOptTable}.
*/
public class RelOptTableImpl extends Prepare.AbstractPreparingTable {
private final @Nullable RelOptSchema schema;
private final RelDataType rowType;
private final @Nullable Table table;
private final @Nullable TableExpressionFactory tableExpressionFactory;
private final ImmutableList<String> names;
/** Estimate for the row count, or null.
*
* <p>If not null, overrides the estimate from the actual table.
*
* <p>Useful when a table that contains a materialized query result is being
* used to replace a query expression that wildly underestimates the row
* count. Now the materialized table can tell the same lie. */
private final @Nullable Double rowCount;
private RelOptTableImpl(
@Nullable RelOptSchema schema,
RelDataType rowType,
List<String> names,
@Nullable Table table,
@Nullable TableExpressionFactory tableExpressionFactory,
@Nullable Double rowCount) {
this.schema = schema;
this.rowType = requireNonNull(rowType, "rowType");
this.names = ImmutableList.copyOf(names);
this.table = table; // may be null
this.tableExpressionFactory = tableExpressionFactory; // may be null
this.rowCount = rowCount; // may be null
}
public static RelOptTableImpl create(
@Nullable RelOptSchema schema,
RelDataType rowType,
List<String> names,
Expression expression) {
return new RelOptTableImpl(schema, rowType, names, null,
c -> expression, null);
}
@Deprecated // to be removed before 2.0
public static RelOptTableImpl create(
@Nullable RelOptSchema schema,
RelDataType rowType,
List<String> names,
Table table,
Expression expression) {
return create(schema, rowType, names, table, c -> expression);
}
/**
* Creates {@link RelOptTableImpl} instance with specified arguments
* and row count obtained from table statistic.
*
* @param schema table schema
* @param rowType table row type
* @param names full table path
* @param table table
* @param expressionFactory expression function for accessing table data
* in the generated code
*
* @return {@link RelOptTableImpl} instance
*/
public static RelOptTableImpl create(
@Nullable RelOptSchema schema,
RelDataType rowType,
List<String> names,
Table table,
TableExpressionFactory expressionFactory) {
return new RelOptTableImpl(schema, rowType, names, table,
expressionFactory, table.getStatistic().getRowCount());
}
public static RelOptTableImpl create(@Nullable RelOptSchema schema, RelDataType rowType,
Table table, Path path) {
final SchemaPlus schemaPlus = MySchemaPlus.create(path);
return new RelOptTableImpl(schema, rowType, Pair.left(path), table,
c -> Schemas.getTableExpression(schemaPlus, Util.last(path).left, table, c),
table.getStatistic().getRowCount());
}
public static RelOptTableImpl create(@Nullable RelOptSchema schema, RelDataType rowType,
final CalciteSchema.TableEntry tableEntry, @Nullable Double rowCount) {
final Table table = tableEntry.getTable();
return new RelOptTableImpl(schema, rowType, tableEntry.path(), table,
c -> Schemas.getTableExpression(tableEntry.schema.plus(), tableEntry.name, table, c),
rowCount);
}
/**
* Creates a copy of this RelOptTable. The new RelOptTable will have newRowType.
*/
public RelOptTableImpl copy(RelDataType newRowType) {
return new RelOptTableImpl(this.schema, newRowType, this.names, this.table,
this.tableExpressionFactory, this.rowCount);
}
@Override public String toString() {
return "RelOptTableImpl{"
+ "schema=" + schema
+ ", names= " + names
+ ", table=" + table
+ ", rowType=" + rowType
+ '}';
}
public static RelOptTableImpl create(@Nullable RelOptSchema schema,
RelDataType rowType, Table table, ImmutableList<String> names) {
assert table instanceof TranslatableTable
|| table instanceof ScannableTable
|| table instanceof ModifiableTable;
return new RelOptTableImpl(schema, rowType, names, table, null, null);
}
@Override public <T extends Object> @Nullable T unwrap(Class<T> clazz) {
if (clazz.isInstance(this)) {
return clazz.cast(this);
}
if (clazz.isInstance(table)) {
return clazz.cast(table);
}
if (table instanceof Wrapper) {
final T t = ((Wrapper) table).unwrap(clazz);
if (t != null) {
return t;
}
}
if (clazz == CalciteSchema.class && schema != null) {
return clazz.cast(
Schemas.subSchema(((CalciteCatalogReader) schema).rootSchema,
Util.skipLast(getQualifiedName())));
}
return null;
}
@Override public @Nullable Expression getExpression(Class clazz) {
if (tableExpressionFactory == null) {
return null;
}
return tableExpressionFactory.create(clazz);
}
@Override protected RelOptTable extend(Table extendedTable) {
RelOptSchema schema = requireNonNull(getRelOptSchema(), "relOptSchema");
final RelDataType extendedRowType =
extendedTable.getRowType(schema.getTypeFactory());
return new RelOptTableImpl(schema, extendedRowType, getQualifiedName(),
extendedTable, tableExpressionFactory, getRowCount());
}
@Override public boolean equals(@Nullable Object obj) {
return obj instanceof RelOptTableImpl
&& this.rowType.equals(((RelOptTableImpl) obj).getRowType())
&& this.table == ((RelOptTableImpl) obj).table;
}
@Override public int hashCode() {
return (this.table == null)
? super.hashCode() : this.table.hashCode();
}
@Override public double getRowCount() {
if (rowCount != null) {
return rowCount;
}
if (table != null) {
final Double rowCount = table.getStatistic().getRowCount();
if (rowCount != null) {
return rowCount;
}
}
return 100d;
}
@Override public @Nullable RelOptSchema getRelOptSchema() {
return schema;
}
@Override public RelNode toRel(ToRelContext context) {
// Make sure rowType's list is immutable. If rowType is DynamicRecordType, creates a new
// RelOptTable by replacing with immutable RelRecordType using the same field list.
if (this.getRowType().isDynamicStruct()) {
final RelDataType staticRowType = new RelRecordType(getRowType().getFieldList());
final RelOptTable relOptTable = this.copy(staticRowType);
return relOptTable.toRel(context);
}
// If there are any virtual columns, create a copy of this table without
// those virtual columns.
final List<ColumnStrategy> strategies = getColumnStrategies();
if (strategies.contains(ColumnStrategy.VIRTUAL)) {
final RelDataTypeFactory.Builder b =
context.getCluster().getTypeFactory().builder();
for (RelDataTypeField field : rowType.getFieldList()) {
if (strategies.get(field.getIndex()) != ColumnStrategy.VIRTUAL) {
b.add(field.getName(), field.getType());
}
}
final RelOptTable relOptTable =
new RelOptTableImpl(this.schema, b.build(), this.names, this.table,
this.tableExpressionFactory, this.rowCount) {
@Override public <T extends Object> @Nullable T unwrap(Class<T> clazz) {
if (clazz.isAssignableFrom(InitializerExpressionFactory.class)) {
return clazz.cast(NullInitializerExpressionFactory.INSTANCE);
}
return super.unwrap(clazz);
}
};
return relOptTable.toRel(context);
}
if (table instanceof TranslatableTable) {
return ((TranslatableTable) table).toRel(context, this);
}
return LogicalTableScan.create(context.getCluster(), this, context.getTableHints());
}
@Override public @Nullable List<RelCollation> getCollationList() {
if (table != null) {
return table.getStatistic().getCollations();
}
return ImmutableList.of();
}
@Override public @Nullable RelDistribution getDistribution() {
if (table != null) {
return table.getStatistic().getDistribution();
}
return RelDistributionTraitDef.INSTANCE.getDefault();
}
@Override public boolean isKey(ImmutableBitSet columns) {
if (table != null) {
return table.getStatistic().isKey(columns);
}
return false;
}
@Override public @Nullable List<ImmutableBitSet> getKeys() {
if (table != null) {
return table.getStatistic().getKeys();
}
return ImmutableList.of();
}
@Override public @Nullable List<RelReferentialConstraint> getReferentialConstraints() {
if (table != null) {
return table.getStatistic().getReferentialConstraints();
}
return ImmutableList.of();
}
@Override public RelDataType getRowType() {
return rowType;
}
@Override public boolean supportsModality(SqlModality modality) {
switch (modality) {
case STREAM:
return table instanceof StreamableTable;
default:
return !(table instanceof StreamableTable);
}
}
@Override public boolean isTemporal() {
return table instanceof TemporalTable;
}
@Override public List<String> getQualifiedName() {
return names;
}
@Override public SqlMonotonicity getMonotonicity(String columnName) {
if (table == null) {
return SqlMonotonicity.NOT_MONOTONIC;
}
List<RelCollation> collations = table.getStatistic().getCollations();
if (collations == null) {
return SqlMonotonicity.NOT_MONOTONIC;
}
for (RelCollation collation : collations) {
final RelFieldCollation fieldCollation =
collation.getFieldCollations().get(0);
final int fieldIndex = fieldCollation.getFieldIndex();
if (fieldIndex < rowType.getFieldCount()
&& rowType.getFieldNames().get(fieldIndex).equals(columnName)) {
return fieldCollation.direction.monotonicity();
}
}
return SqlMonotonicity.NOT_MONOTONIC;
}
@Override public SqlAccessType getAllowedAccess() {
return SqlAccessType.ALL;
}
/** Helper for {@link #getColumnStrategies()}. */
public static List<ColumnStrategy> columnStrategies(final RelOptTable table) {
final int fieldCount = table.getRowType().getFieldCount();
final InitializerExpressionFactory ief =
Util.first(table.unwrap(InitializerExpressionFactory.class),
NullInitializerExpressionFactory.INSTANCE);
return new AbstractList<ColumnStrategy>() {
@Override public int size() {
return fieldCount;
}
@Override public ColumnStrategy get(int index) {
return ief.generationStrategy(table, index);
}
};
}
/** Converts the ordinal of a field into the ordinal of a stored field.
* That is, it subtracts the number of virtual fields that come before it. */
public static int realOrdinal(final RelOptTable table, int i) {
List<ColumnStrategy> strategies = table.getColumnStrategies();
int n = 0;
for (int j = 0; j < i; j++) {
switch (strategies.get(j)) {
case VIRTUAL:
++n;
break;
default:
break;
}
}
return i - n;
}
/** Returns the row type of a table after any {@link ColumnStrategy#VIRTUAL}
* columns have been removed. This is the type of the records that are
* actually stored. */
public static RelDataType realRowType(RelOptTable table) {
final RelDataType rowType = table.getRowType();
final List<ColumnStrategy> strategies = columnStrategies(table);
if (!strategies.contains(ColumnStrategy.VIRTUAL)) {
return rowType;
}
final RelDataTypeFactory.Builder builder =
requireNonNull(table.getRelOptSchema(),
() -> "relOptSchema for table " + table).getTypeFactory().builder();
for (RelDataTypeField field : rowType.getFieldList()) {
if (strategies.get(field.getIndex()) != ColumnStrategy.VIRTUAL) {
builder.add(field);
}
}
return builder.build();
}
/** Implementation of {@link SchemaPlus} that wraps a regular schema and knows
* its name and parent.
*
* <p>It is read-only, and functionality is limited in other ways, it but
* allows table expressions to be generated. */
private static class MySchemaPlus implements SchemaPlus {
private final @Nullable SchemaPlus parent;
private final String name;
private final Schema schema;
MySchemaPlus(@Nullable SchemaPlus parent, String name, Schema schema) {
this.parent = parent;
this.name = name;
this.schema = schema;
}
public static MySchemaPlus create(Path path) {
final Pair<String, Schema> pair = Util.last(path);
final SchemaPlus parent;
if (path.size() == 1) {
parent = null;
} else {
parent = create(path.parent());
}
return new MySchemaPlus(parent, pair.left, pair.right);
}
@Override public @Nullable SchemaPlus getParentSchema() {
return parent;
}
@Override public String getName() {
return name;
}
@Override public @Nullable SchemaPlus getSubSchema(String name) {
final Schema subSchema = schema.getSubSchema(name);
return subSchema == null ? null : new MySchemaPlus(this, name, subSchema);
}
@Override public SchemaPlus add(String name, Schema schema) {
throw new UnsupportedOperationException();
}
@Override public void add(String name, Table table) {
throw new UnsupportedOperationException();
}
@Override public boolean removeTable(String name) {
throw new UnsupportedOperationException();
}
@Override public void add(String name,
org.apache.calcite.schema.Function function) {
throw new UnsupportedOperationException();
}
@Override public void add(String name, RelProtoDataType type) {
throw new UnsupportedOperationException();
}
@Override public void add(String name, Lattice lattice) {
throw new UnsupportedOperationException();
}
@Override public boolean isMutable() {
return schema.isMutable();
}
@Override public <T extends Object> @Nullable T unwrap(Class<T> clazz) {
return null;
}
@Override public void setPath(ImmutableList<ImmutableList<String>> path) {
throw new UnsupportedOperationException();
}
@Override public void setCacheEnabled(boolean cache) {
throw new UnsupportedOperationException();
}
@Override public boolean isCacheEnabled() {
return false;
}
@Override public @Nullable Table getTable(String name) {
return schema.getTable(name);
}
@Override public Set<String> getTableNames() {
return schema.getTableNames();
}
@Override public @Nullable RelProtoDataType getType(String name) {
return schema.getType(name);
}
@Override public Set<String> getTypeNames() {
return schema.getTypeNames();
}
@Override public Collection<org.apache.calcite.schema.Function>
getFunctions(String name) {
return schema.getFunctions(name);
}
@Override public Set<String> getFunctionNames() {
return schema.getFunctionNames();
}
@Override public Set<String> getSubSchemaNames() {
return schema.getSubSchemaNames();
}
@Override public Expression getExpression(@Nullable SchemaPlus parentSchema,
String name) {
return schema.getExpression(parentSchema, name);
}
@Override public Schema snapshot(SchemaVersion version) {
throw new UnsupportedOperationException();
}
}
}