| /* |
| * 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.adapter.enumerable; |
| |
| import org.apache.calcite.adapter.java.JavaTypeFactory; |
| import org.apache.calcite.config.CalciteSystemProperty; |
| import org.apache.calcite.interpreter.Row; |
| import org.apache.calcite.linq4j.Enumerable; |
| import org.apache.calcite.linq4j.Queryable; |
| import org.apache.calcite.linq4j.function.Function1; |
| import org.apache.calcite.linq4j.tree.Blocks; |
| import org.apache.calcite.linq4j.tree.Expression; |
| import org.apache.calcite.linq4j.tree.Expressions; |
| import org.apache.calcite.linq4j.tree.MethodCallExpression; |
| import org.apache.calcite.linq4j.tree.ParameterExpression; |
| import org.apache.calcite.linq4j.tree.Primitive; |
| import org.apache.calcite.linq4j.tree.Types; |
| import org.apache.calcite.plan.DeriveMode; |
| import org.apache.calcite.plan.RelOptCluster; |
| import org.apache.calcite.plan.RelOptTable; |
| import org.apache.calcite.plan.RelOptUtil; |
| import org.apache.calcite.plan.RelTraitSet; |
| import org.apache.calcite.rel.RelCollationTraitDef; |
| import org.apache.calcite.rel.RelNode; |
| import org.apache.calcite.rel.core.TableScan; |
| import org.apache.calcite.rel.type.RelDataType; |
| import org.apache.calcite.rel.type.RelDataTypeField; |
| import org.apache.calcite.schema.FilterableTable; |
| import org.apache.calcite.schema.ProjectableFilterableTable; |
| import org.apache.calcite.schema.QueryableTable; |
| import org.apache.calcite.schema.ScannableTable; |
| import org.apache.calcite.schema.StreamableTable; |
| import org.apache.calcite.schema.Table; |
| import org.apache.calcite.schema.TransientTable; |
| import org.apache.calcite.sql.SqlExplainLevel; |
| import org.apache.calcite.util.BuiltInMethod; |
| |
| import com.google.common.collect.ImmutableList; |
| |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| |
| import java.lang.reflect.Type; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import static org.apache.calcite.linq4j.tree.Types.toClass; |
| |
| import static java.util.Objects.requireNonNull; |
| |
| /** Implementation of {@link org.apache.calcite.rel.core.TableScan} in |
| * {@link org.apache.calcite.adapter.enumerable.EnumerableConvention enumerable calling convention}. */ |
| public class EnumerableTableScan |
| extends TableScan |
| implements EnumerableRel { |
| private final Class elementType; |
| |
| /** Creates an EnumerableTableScan. |
| * |
| * <p>Use {@link #create} unless you know what you are doing. */ |
| public EnumerableTableScan(RelOptCluster cluster, RelTraitSet traitSet, |
| RelOptTable table, Class elementType) { |
| super(cluster, traitSet, ImmutableList.of(), table); |
| assert getConvention() instanceof EnumerableConvention; |
| this.elementType = elementType; |
| assert canHandle(table) |
| : "EnumerableTableScan can't implement " + table + ", see EnumerableTableScan#canHandle"; |
| } |
| |
| /** |
| * Code snippet to demonstrate how to generate IndexScan on demand |
| * by passing required collation through TableScan. |
| * |
| * @return IndexScan if there is index available on collation keys |
| */ |
| @Override public @Nullable RelNode passThrough(final RelTraitSet required) { |
| /* |
| keys = required.getCollation().getKeys(); |
| if (table has index on keys) { |
| direction = forward or backward; |
| return new IndexScan(table, indexInfo, direction); |
| } |
| */ |
| return null; |
| } |
| |
| @Override public DeriveMode getDeriveMode() { |
| return DeriveMode.PROHIBITED; |
| } |
| |
| /** Creates an EnumerableTableScan. */ |
| public static EnumerableTableScan create(RelOptCluster cluster, |
| RelOptTable relOptTable) { |
| final Table table = relOptTable.unwrap(Table.class); |
| Class elementType = EnumerableTableScan.deduceElementType(table); |
| final RelTraitSet traitSet = |
| cluster.traitSetOf(EnumerableConvention.INSTANCE) |
| .replaceIfs(RelCollationTraitDef.INSTANCE, () -> { |
| if (table != null) { |
| return table.getStatistic().getCollations(); |
| } |
| return ImmutableList.of(); |
| }); |
| return new EnumerableTableScan(cluster, traitSet, relOptTable, elementType); |
| } |
| |
| /** Returns whether EnumerableTableScan can generate code to handle a |
| * particular variant of the Table SPI. |
| * |
| * @deprecated remove before Calcite 2.0 |
| */ |
| @Deprecated |
| public static boolean canHandle(Table table) { |
| if (table instanceof TransientTable) { |
| // CALCITE-3673: TransientTable can't be implemented with Enumerable |
| return false; |
| } |
| // See org.apache.calcite.prepare.RelOptTableImpl.getClassExpressionFunction |
| return table instanceof QueryableTable |
| || table instanceof FilterableTable |
| || table instanceof ProjectableFilterableTable |
| || table instanceof ScannableTable; |
| } |
| |
| /** Returns whether EnumerableTableScan can generate code to handle a |
| * particular variant of the Table SPI. */ |
| public static boolean canHandle(RelOptTable relOptTable) { |
| Table table = relOptTable.unwrap(Table.class); |
| if (table != null && !canHandle(table)) { |
| return false; |
| } |
| boolean supportArray = CalciteSystemProperty.ENUMERABLE_ENABLE_TABLESCAN_ARRAY.value(); |
| boolean supportMap = CalciteSystemProperty.ENUMERABLE_ENABLE_TABLESCAN_MAP.value(); |
| boolean supportMultiset = CalciteSystemProperty.ENUMERABLE_ENABLE_TABLESCAN_MULTISET.value(); |
| if (supportArray && supportMap && supportMultiset) { |
| return true; |
| } |
| // Struct fields are not supported in EnumerableTableScan |
| for (RelDataTypeField field : relOptTable.getRowType().getFieldList()) { |
| boolean unsupportedType = false; |
| switch (field.getType().getSqlTypeName()) { |
| case ARRAY: |
| unsupportedType = supportArray; |
| break; |
| case MAP: |
| unsupportedType = supportMap; |
| break; |
| case MULTISET: |
| unsupportedType = supportMultiset; |
| break; |
| default: |
| break; |
| } |
| if (unsupportedType) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public static Class deduceElementType(@Nullable Table table) { |
| if (table instanceof QueryableTable) { |
| final QueryableTable queryableTable = (QueryableTable) table; |
| final Type type = queryableTable.getElementType(); |
| if (type instanceof Class) { |
| return (Class) type; |
| } else { |
| return Object[].class; |
| } |
| } else if (table instanceof ScannableTable |
| || table instanceof FilterableTable |
| || table instanceof ProjectableFilterableTable |
| || table instanceof StreamableTable) { |
| return Object[].class; |
| } else { |
| return Object.class; |
| } |
| } |
| |
| public static JavaRowFormat deduceFormat(RelOptTable table) { |
| final Class elementType = deduceElementType(table.unwrapOrThrow(Table.class)); |
| return elementType == Object[].class |
| ? JavaRowFormat.ARRAY |
| : JavaRowFormat.CUSTOM; |
| } |
| |
| private Expression getExpression(PhysType physType) { |
| final Expression expression = table.getExpression(Queryable.class); |
| if (expression == null) { |
| throw new IllegalStateException( |
| "Unable to implement " + RelOptUtil.toString(this, SqlExplainLevel.ALL_ATTRIBUTES) |
| + ": " + table + ".getExpression(Queryable.class) returned null"); |
| } |
| final Expression expression2 = toEnumerable(expression); |
| assert Types.isAssignableFrom(Enumerable.class, expression2.getType()); |
| return toRows(physType, expression2); |
| } |
| |
| private static Expression toEnumerable(Expression expression) { |
| final Type type = expression.getType(); |
| if (Types.isArray(type)) { |
| if (requireNonNull(toClass(type).getComponentType()).isPrimitive()) { |
| expression = |
| Expressions.call(BuiltInMethod.AS_LIST.method, expression); |
| } |
| return Expressions.call(BuiltInMethod.AS_ENUMERABLE.method, expression); |
| } else if (Types.isAssignableFrom(Iterable.class, type) |
| && !Types.isAssignableFrom(Enumerable.class, type)) { |
| return Expressions.call(BuiltInMethod.AS_ENUMERABLE2.method, |
| expression); |
| } else if (Types.isAssignableFrom(Queryable.class, type)) { |
| // Queryable extends Enumerable, but it's too "clever", so we call |
| // Queryable.asEnumerable so that operations such as take(int) will be |
| // evaluated directly. |
| return Expressions.call(expression, |
| BuiltInMethod.QUERYABLE_AS_ENUMERABLE.method); |
| } |
| return expression; |
| } |
| |
| private Expression toRows(PhysType physType, Expression expression) { |
| if (physType.getFormat() == JavaRowFormat.SCALAR |
| && Object[].class.isAssignableFrom(elementType) |
| && getRowType().getFieldCount() == 1 |
| && (table.unwrap(ScannableTable.class) != null |
| || table.unwrap(FilterableTable.class) != null |
| || table.unwrap(ProjectableFilterableTable.class) != null)) { |
| return Expressions.call(BuiltInMethod.SLICE0.method, expression); |
| } |
| JavaRowFormat oldFormat = format(); |
| if (physType.getFormat() == oldFormat && !hasCollectionField(getRowType())) { |
| return expression; |
| } |
| final ParameterExpression row_ = |
| Expressions.parameter(elementType, "row"); |
| final int fieldCount = table.getRowType().getFieldCount(); |
| List<Expression> expressionList = new ArrayList<>(fieldCount); |
| for (int i = 0; i < fieldCount; i++) { |
| expressionList.add(fieldExpression(row_, i, physType, oldFormat)); |
| } |
| return Expressions.call(expression, |
| BuiltInMethod.SELECT.method, |
| Expressions.lambda(Function1.class, physType.record(expressionList), |
| row_)); |
| } |
| |
| private Expression fieldExpression(ParameterExpression row_, int i, |
| PhysType physType, JavaRowFormat format) { |
| final Expression e = |
| format.field(row_, i, null, physType.getJavaFieldType(i)); |
| final RelDataType relFieldType = |
| physType.getRowType().getFieldList().get(i).getType(); |
| switch (relFieldType.getSqlTypeName()) { |
| case ARRAY: |
| case MULTISET: |
| final RelDataType fieldType = |
| requireNonNull(relFieldType.getComponentType(), |
| () -> "relFieldType.getComponentType() for " + relFieldType); |
| if (fieldType.isStruct()) { |
| // We can't represent a multiset or array as a List<Employee>, because |
| // the consumer does not know the element type. |
| // The standard element type is List. |
| // We need to convert to a List<List>. |
| final JavaTypeFactory typeFactory = |
| (JavaTypeFactory) getCluster().getTypeFactory(); |
| final PhysType elementPhysType = |
| PhysTypeImpl.of(typeFactory, fieldType, JavaRowFormat.CUSTOM); |
| final MethodCallExpression e2 = |
| Expressions.call(BuiltInMethod.AS_ENUMERABLE2.method, e); |
| final Expression e3 = elementPhysType.convertTo(e2, JavaRowFormat.LIST); |
| return Expressions.call(e3, BuiltInMethod.ENUMERABLE_TO_LIST.method); |
| } else { |
| return e; |
| } |
| default: |
| return e; |
| } |
| } |
| |
| private JavaRowFormat format() { |
| int fieldCount = getRowType().getFieldCount(); |
| if (fieldCount == 0) { |
| return JavaRowFormat.LIST; |
| } |
| if (Object[].class.isAssignableFrom(elementType)) { |
| return fieldCount == 1 ? JavaRowFormat.SCALAR : JavaRowFormat.ARRAY; |
| } |
| if (Row.class.isAssignableFrom(elementType)) { |
| return JavaRowFormat.ROW; |
| } |
| if (fieldCount == 1 && (Object.class == elementType |
| || Primitive.is(elementType) |
| || Number.class.isAssignableFrom(elementType) |
| || String.class == elementType)) { |
| return JavaRowFormat.SCALAR; |
| } |
| return JavaRowFormat.CUSTOM; |
| } |
| |
| private static boolean hasCollectionField(RelDataType rowType) { |
| for (RelDataTypeField field : rowType.getFieldList()) { |
| switch (field.getType().getSqlTypeName()) { |
| case ARRAY: |
| case MULTISET: |
| return true; |
| default: |
| break; |
| } |
| } |
| return false; |
| } |
| |
| @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) { |
| return new EnumerableTableScan(getCluster(), traitSet, table, elementType); |
| } |
| |
| @Override public Result implement(EnumerableRelImplementor implementor, Prefer pref) { |
| // Note that representation is ARRAY. This assumes that the table |
| // returns a Object[] for each record. Actually a Table<T> can |
| // return any type T. And, if it is a JdbcTable, we'd like to be |
| // able to generate alternate accessors that return e.g. synthetic |
| // records {T0 f0; T1 f1; ...} and don't box every primitive value. |
| final PhysType physType = |
| PhysTypeImpl.of( |
| implementor.getTypeFactory(), |
| getRowType(), |
| format()); |
| final Expression expression = getExpression(physType); |
| return implementor.result(physType, Blocks.toBlock(expression)); |
| } |
| } |