/* * 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.drill.exec.planner.sql.handlers; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import org.apache.calcite.plan.RelTraitSet; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.RelShuttleImpl; import org.apache.calcite.rel.logical.LogicalAggregate; import org.apache.calcite.rel.logical.LogicalIntersect; import org.apache.calcite.rel.logical.LogicalJoin; import org.apache.calcite.rel.logical.LogicalMinus; import org.apache.calcite.rel.logical.LogicalSort; import org.apache.calcite.rel.logical.LogicalUnion; import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rex.RexLiteral; import org.apache.calcite.rex.RexNode; import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.type.SqlTypeName; import org.apache.drill.common.exceptions.ExecutionSetupException; import org.apache.drill.common.types.TypeProtos; import org.apache.drill.exec.exception.SchemaChangeException; import org.apache.drill.exec.expr.TypeHelper; import org.apache.drill.exec.ops.OperatorContext; import org.apache.drill.exec.physical.base.ScanStats; import org.apache.drill.exec.physical.impl.OutputMutator; import org.apache.drill.exec.planner.logical.DrillDirectScanRel; import org.apache.drill.exec.planner.logical.DrillLimitRel; import org.apache.drill.exec.planner.logical.DrillRel; import org.apache.drill.exec.planner.sql.TypeInferenceUtils; import org.apache.drill.exec.record.MaterializedField; import org.apache.drill.exec.store.AbstractRecordReader; import org.apache.drill.exec.store.direct.DirectGroupScan; import java.util.List; /** * Visitor that will identify whether the root portion of the RelNode tree contains a limit 0 pattern. In this case, we * inform the planner settings that this plan should be run as a single node plan to reduce the overhead associated with * executing a schema-only query. */ public class FindLimit0Visitor extends RelShuttleImpl { // private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(FindLimit0Visitor.class); // Some types are excluded in this set: // + DECIMAL type is not fully supported in general. // + VARBINARY is not fully tested. // + MAP, ARRAY are currently not exposed to the planner. // + TINYINT, SMALLINT are defined in the Drill type system but have been turned off for now. // + SYMBOL, MULTISET, DISTINCT, STRUCTURED, ROW, OTHER, CURSOR, COLUMN_LIST are Calcite types // currently not supported by Drill, nor defined in the Drill type list. // + ANY is the late binding type. private static final ImmutableSet<SqlTypeName> TYPES = ImmutableSet.<SqlTypeName>builder() .add(SqlTypeName.INTEGER, SqlTypeName.BIGINT, SqlTypeName.FLOAT, SqlTypeName.DOUBLE, SqlTypeName.VARCHAR, SqlTypeName.BOOLEAN, SqlTypeName.DATE, SqlTypeName.TIME, SqlTypeName.TIMESTAMP, SqlTypeName.INTERVAL_YEAR_MONTH, SqlTypeName.INTERVAL_DAY_TIME, SqlTypeName.CHAR) .build(); /** * If all field types of the given node are {@link #TYPES recognized types} and honored by execution, then this * method returns the tree: DrillDirectScanRel(field types). Otherwise, the method returns null. * * @param rel calcite logical rel tree * @return drill logical rel tree */ public static DrillRel getDirectScanRelIfFullySchemaed(RelNode rel) { final List<RelDataTypeField> fieldList = rel.getRowType().getFieldList(); final List<TypeProtos.MajorType> columnTypes = Lists.newArrayList(); for (final RelDataTypeField field : fieldList) { final SqlTypeName sqlTypeName = field.getType().getSqlTypeName(); if (!TYPES.contains(sqlTypeName)) { return null; } else { final TypeProtos.MajorType.Builder builder = TypeProtos.MajorType.newBuilder() .setMode(field.getType().isNullable() ? TypeProtos.DataMode.OPTIONAL : TypeProtos.DataMode.REQUIRED) .setMinorType(TypeInferenceUtils.getDrillTypeFromCalciteType(sqlTypeName)); if (TypeInferenceUtils.isScalarStringType(sqlTypeName)) { builder.setPrecision(field.getType().getPrecision()); } columnTypes.add(builder.build()); } } final RelTraitSet traits = rel.getTraitSet().plus(DrillRel.DRILL_LOGICAL); final RelDataTypeReader reader = new RelDataTypeReader(rel.getRowType().getFieldNames(), columnTypes); return new DrillDirectScanRel(rel.getCluster(), traits, new DirectGroupScan(reader, ScanStats.ZERO_RECORD_TABLE), rel.getRowType()); } /** * Check if the root portion of the tree contains LIMIT(0). * * @param rel rel node tree * @return true if the root portion of the tree contains LIMIT(0) */ public static boolean containsLimit0(final RelNode rel) { FindLimit0Visitor visitor = new FindLimit0Visitor(); rel.accept(visitor); return visitor.isContains(); } private boolean contains = false; private FindLimit0Visitor() { } boolean isContains() { return contains; } private static boolean isLimit0(RexNode fetch) { if (fetch != null && fetch.isA(SqlKind.LITERAL)) { RexLiteral l = (RexLiteral) fetch; switch (l.getTypeName()) { case BIGINT: case INTEGER: case DECIMAL: if (((long) l.getValue2()) == 0) { return true; } } } return false; } @Override public RelNode visit(LogicalSort sort) { if (isLimit0(sort.fetch)) { contains = true; return sort; } return super.visit(sort); } @Override public RelNode visit(RelNode other) { if (other instanceof DrillLimitRel) { if (isLimit0(((DrillLimitRel) other).getFetch())) { contains = true; return other; } } return super.visit(other); } // The following set of RelNodes should terminate a search for the limit 0 pattern as they want convey its meaning. @Override public RelNode visit(LogicalAggregate aggregate) { return aggregate; } @Override public RelNode visit(LogicalIntersect intersect) { return intersect; } @Override public RelNode visit(LogicalJoin join) { return join; } @Override public RelNode visit(LogicalMinus minus) { return minus; } @Override public RelNode visit(LogicalUnion union) { return union; } /** * Reader for column names and types. */ public static class RelDataTypeReader extends AbstractRecordReader { public final List<String> columnNames; public final List<TypeProtos.MajorType> columnTypes; public RelDataTypeReader(List<String> columnNames, List<TypeProtos.MajorType> columnTypes) { Preconditions.checkArgument(columnNames.size() == columnTypes.size(), "Number of columns and their types should match"); this.columnNames = columnNames; this.columnTypes = columnTypes; } @Override public void setup(OperatorContext context, OutputMutator output) throws ExecutionSetupException { for (int i = 0; i < columnNames.size(); i++) { final TypeProtos.MajorType type = columnTypes.get(i); final MaterializedField field = MaterializedField.create(columnNames.get(i), type); final Class vvClass = TypeHelper.getValueVectorClass(type.getMinorType(), type.getMode()); try { output.addField(field, vvClass); } catch (SchemaChangeException e) { throw new ExecutionSetupException(e); } } } @Override public int next() { return 0; } @Override public void close() throws Exception { } } }