| /* |
| * 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.index; |
| |
| import com.fasterxml.jackson.annotation.JsonIgnore; |
| import com.fasterxml.jackson.annotation.JsonProperty; |
| import com.google.common.collect.Sets; |
| |
| import org.apache.calcite.rel.RelCollation; |
| import org.apache.calcite.rel.RelCollations; |
| import org.apache.calcite.rel.RelFieldCollation; |
| import org.apache.calcite.rel.RelFieldCollation.NullDirection; |
| import org.apache.drill.common.expression.CastExpression; |
| import org.apache.drill.common.expression.LogicalExpression; |
| import org.apache.drill.common.expression.SchemaPath; |
| |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| public class DrillIndexDefinition implements IndexDefinition { |
| /** |
| * The indexColumns is the list of column(s) on which this index is created. If there is more than 1 column, |
| * the order of the columns is important: index on {a, b} is not the same as index on {b, a} |
| * NOTE: the indexed column could be of type columnfamily.column |
| */ |
| @JsonProperty |
| protected final List<LogicalExpression> indexColumns; |
| |
| /** |
| * nonIndexColumns: the list of columns that are included in the index as 'covering' |
| * columns but are not themselves indexed. These are useful for covering indexes where the |
| * query request can be satisfied directly by the index and avoid accessing the table altogether. |
| */ |
| @JsonProperty |
| protected final List<LogicalExpression> nonIndexColumns; |
| |
| @JsonIgnore |
| protected final Set<LogicalExpression> allIndexColumns; |
| |
| @JsonProperty |
| protected final List<LogicalExpression> rowKeyColumns; |
| |
| @JsonProperty |
| protected final CollationContext indexCollationContext; |
| |
| /** |
| * indexName: name of the index that should be unique within the scope of a table |
| */ |
| @JsonProperty |
| protected final String indexName; |
| |
| protected final String tableName; |
| |
| @JsonProperty |
| protected final IndexDescriptor.IndexType indexType; |
| |
| @JsonProperty |
| protected final NullDirection nullsDirection; |
| |
| public DrillIndexDefinition(List<LogicalExpression> indexCols, |
| CollationContext indexCollationContext, |
| List<LogicalExpression> nonIndexCols, |
| List<LogicalExpression> rowKeyColumns, |
| String indexName, |
| String tableName, |
| IndexType type, |
| NullDirection nullsDirection) { |
| this.indexColumns = indexCols; |
| this.nonIndexColumns = nonIndexCols; |
| this.rowKeyColumns = rowKeyColumns; |
| this.indexName = indexName; |
| this.tableName = tableName; |
| this.indexType = type; |
| this.allIndexColumns = Sets.newHashSet(indexColumns); |
| this.allIndexColumns.addAll(nonIndexColumns); |
| this.indexCollationContext = indexCollationContext; |
| this.nullsDirection = nullsDirection; |
| |
| } |
| |
| @Override |
| public int getIndexColumnOrdinal(LogicalExpression path) { |
| int id = indexColumns.indexOf(path); |
| return id; |
| } |
| |
| @Override |
| public boolean isCoveringIndex(List<LogicalExpression> columns) { |
| return allIndexColumns.containsAll(columns); |
| } |
| |
| @Override |
| public boolean allColumnsIndexed(Collection<LogicalExpression> columns) { |
| return columnsInIndexFields(columns, indexColumns); |
| } |
| |
| @Override |
| public boolean someColumnsIndexed(Collection<LogicalExpression> columns) { |
| return someColumnsInIndexFields(columns, indexColumns); |
| } |
| |
| public boolean pathExactIn(SchemaPath path, Collection<LogicalExpression> exprs) { |
| for (LogicalExpression expr : exprs) { |
| if (expr instanceof SchemaPath) { |
| if (((SchemaPath) expr).toExpr().equals(path.toExpr())) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| boolean castIsCompatible(CastExpression castExpr, Collection<LogicalExpression> indexFields) { |
| for (LogicalExpression indexExpr : indexFields) { |
| if (indexExpr.getClass() != castExpr.getClass()) { |
| continue; |
| } |
| CastExpression indexCastExpr = (CastExpression)indexExpr; |
| // we compare input using equals because we know we are comparing SchemaPath, |
| // if we extend to support other expression, make sure the equals of that expression |
| // is implemented properly, otherwise it will fall to identity comparison |
| if (!castExpr.getInput().equals(indexCastExpr.getInput()) ) { |
| continue; |
| } |
| |
| if (castExpr.getMajorType().getMinorType() != indexCastExpr.getMajorType().getMinorType()) { |
| continue; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| protected boolean columnsInIndexFields(Collection<LogicalExpression> columns, Collection<LogicalExpression> indexFields) { |
| // we need to do extra check, so we could allow the case when query condition expression is not identical with indexed fields |
| // and they still could use the index either by implicit cast or the difference is allowed, e.g. width of varchar |
| for (LogicalExpression col : columns) { |
| if (col instanceof CastExpression) { |
| if (!castIsCompatible((CastExpression) col, indexFields)) { |
| return false; |
| } |
| } |
| else { |
| if (!pathExactIn((SchemaPath)col, indexFields)) { |
| return false; |
| } |
| } |
| } |
| return true;//indexFields.containsAll(columns); |
| } |
| |
| protected boolean someColumnsInIndexFields(Collection<LogicalExpression> columns, |
| Collection<LogicalExpression> indexFields) { |
| |
| // we need to do extra check, so we could allow the case when query condition expression is not identical with indexed fields |
| // and they still could use the index either by implicit cast or the difference is allowed, e.g. width of varchar |
| for (LogicalExpression col : columns) { |
| if (col instanceof CastExpression) { |
| if (castIsCompatible((CastExpression) col, indexFields)) { |
| return true; |
| } |
| } |
| else { |
| if (pathExactIn((SchemaPath)col, indexFields)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public String toString() { |
| String columnsDesc = " Index columns: " + indexColumns.toString() + " Non-Index columns: " + nonIndexColumns.toString(); |
| String desc = "Table: " + tableName + " Index: " + indexName + columnsDesc; |
| return desc; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) { |
| return true; |
| } else if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| |
| DrillIndexDefinition index1 = (DrillIndexDefinition) o; |
| return tableName.equals(index1.tableName) |
| && indexName.equals(index1.indexName) |
| && indexType.equals(index1.indexType) |
| && indexColumns.equals(index1.indexColumns); |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| final String fullName = tableName + indexName; |
| int result = 1; |
| result = prime * result + fullName.hashCode(); |
| result = prime * result + indexType.hashCode(); |
| |
| return result; |
| } |
| |
| @Override |
| @JsonProperty |
| public String getIndexName() { |
| return indexName; |
| } |
| |
| @Override |
| public String getTableName() { |
| return tableName; |
| } |
| |
| @Override |
| @JsonProperty |
| public IndexDescriptor.IndexType getIndexType() { |
| return indexType; |
| } |
| |
| @Override |
| @JsonProperty |
| public List<LogicalExpression> getRowKeyColumns() { |
| return this.rowKeyColumns; |
| } |
| |
| @Override |
| @JsonProperty |
| public List<LogicalExpression> getIndexColumns() { |
| return this.indexColumns; |
| } |
| |
| @Override |
| @JsonProperty |
| public List<LogicalExpression> getNonIndexColumns() { |
| return this.nonIndexColumns; |
| } |
| |
| @Override |
| @JsonIgnore |
| public RelCollation getCollation() { |
| if (indexCollationContext != null) { |
| return RelCollations.of(indexCollationContext.relFieldCollations); |
| } |
| return null; |
| } |
| |
| @Override |
| @JsonIgnore |
| public Map<LogicalExpression, RelFieldCollation> getCollationMap() { |
| return indexCollationContext.collationMap; |
| } |
| |
| @Override |
| @JsonIgnore |
| public NullDirection getNullsOrderingDirection() { |
| return nullsDirection; |
| } |
| } |