| /* |
| * 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.phoenix.compile; |
| |
| import java.sql.SQLException; |
| import java.util.Map; |
| |
| import org.apache.hadoop.hbase.io.ImmutableBytesWritable; |
| import org.apache.phoenix.parse.ColumnParseNode; |
| import org.apache.phoenix.parse.FamilyWildcardParseNode; |
| import org.apache.phoenix.parse.LiteralParseNode; |
| import org.apache.phoenix.parse.ParseNode; |
| import org.apache.phoenix.parse.ParseNodeFactory; |
| import org.apache.phoenix.parse.ParseNodeRewriter; |
| import org.apache.phoenix.parse.SelectStatement; |
| import org.apache.phoenix.parse.TableName; |
| import org.apache.phoenix.parse.TableWildcardParseNode; |
| import org.apache.phoenix.parse.WildcardParseNode; |
| import org.apache.phoenix.schema.ColumnRef; |
| import org.apache.phoenix.schema.PColumn; |
| import org.apache.phoenix.schema.TableRef; |
| import org.apache.phoenix.schema.types.PDataType; |
| import org.apache.phoenix.util.IndexUtil; |
| |
| public class IndexStatementRewriter extends ParseNodeRewriter { |
| private static final ParseNodeFactory FACTORY = new ParseNodeFactory(); |
| |
| private Map<TableRef, TableRef> multiTableRewriteMap; |
| private final ImmutableBytesWritable ptr = new ImmutableBytesWritable(); |
| private final boolean setTableAlias; |
| |
| public IndexStatementRewriter(ColumnResolver dataResolver, Map<TableRef, TableRef> multiTableRewriteMap, boolean setTableAlias) { |
| super(dataResolver); |
| this.multiTableRewriteMap = multiTableRewriteMap; |
| this.setTableAlias = setTableAlias; |
| } |
| |
| /** |
| * Rewrite the parse node by translating all data table column references to |
| * references to the corresponding index column. |
| * @param node the parse node |
| * @param dataResolver the column resolver |
| * @return new parse node or the same one if nothing was rewritten. |
| * @throws SQLException |
| */ |
| public static ParseNode translate(ParseNode node, ColumnResolver dataResolver) throws SQLException { |
| return rewrite(node, new IndexStatementRewriter(dataResolver, null, false)); |
| } |
| |
| /** |
| * Rewrite the select statement by translating all data table column references to |
| * references to the corresponding index column. |
| * @param statement the select statement |
| * @param dataResolver the column resolver |
| * @return new select statement or the same one if nothing was rewritten. |
| * @throws SQLException |
| */ |
| public static SelectStatement translate(SelectStatement statement, ColumnResolver dataResolver) throws SQLException { |
| return translate(statement, dataResolver, null); |
| } |
| |
| /** |
| * Rewrite the select statement containing multiple tables by translating all |
| * data table column references to references to the corresponding index column. |
| * @param statement the select statement |
| * @param dataResolver the column resolver |
| * @param multiTableRewriteMap the data table to index table map |
| * @return new select statement or the same one if nothing was rewritten. |
| * @throws SQLException |
| */ |
| public static SelectStatement translate(SelectStatement statement, ColumnResolver dataResolver, Map<TableRef, TableRef> multiTableRewriteMap) throws SQLException { |
| return rewrite(statement, new IndexStatementRewriter(dataResolver, multiTableRewriteMap, false)); |
| } |
| |
| @Override |
| public ParseNode visit(ColumnParseNode node) throws SQLException { |
| ColumnRef dataColRef = getResolver().resolveColumn(node.getSchemaName(), node.getTableName(), node.getName()); |
| PColumn dataCol = dataColRef.getColumn(); |
| TableRef dataTableRef = dataColRef.getTableRef(); |
| // Rewrite view constants as literals, as they won't be in the schema for |
| // an index on the view. Our view may be READ_ONLY yet still have inherited |
| // view constants if based on an UPDATABLE view |
| if (dataCol.getViewConstant() != null) { |
| byte[] viewConstant = dataCol.getViewConstant(); |
| // Ignore last byte, as it's only there so we can have a way to differentiate null |
| // from the absence of a value. |
| ptr.set(viewConstant, 0, viewConstant.length-1); |
| Object literal = dataCol.getDataType().toObject(ptr); |
| return new LiteralParseNode(literal, dataCol.getDataType()); |
| } |
| TableName tName = getReplacedTableName(dataTableRef); |
| if (multiTableRewriteMap != null && tName == null) |
| return node; |
| |
| String indexColName = IndexUtil.getIndexColumnName(dataCol); |
| ParseNode indexColNode = new ColumnParseNode(tName, '"' + indexColName + '"', node.getAlias()); |
| PDataType indexColType = IndexUtil.getIndexColumnDataType(dataCol); |
| PDataType dataColType = dataColRef.getColumn().getDataType(); |
| |
| // Coerce index column reference back to same type as data column so that |
| // expression behave exactly the same. No need to invert, as this will be done |
| // automatically as needed. If node is used at the top level, do not convert, as |
| // otherwise the wrapper gets in the way in the group by clause. For example, |
| // an INTEGER column in a GROUP BY gets doubly wrapped like this: |
| // CAST CAST int_col AS INTEGER AS DECIMAL |
| // This is unnecessary and problematic in the case of a null value. |
| // TODO: test case for this |
| if (!isTopLevel() && indexColType != dataColType) { |
| indexColNode = FACTORY.cast(indexColNode, dataColType, null, null); |
| } |
| return indexColNode; |
| } |
| |
| @Override |
| public ParseNode visit(WildcardParseNode node) throws SQLException { |
| return multiTableRewriteMap != null ? node : WildcardParseNode.REWRITE_INSTANCE; |
| } |
| |
| @Override |
| public ParseNode visit(TableWildcardParseNode node) throws SQLException { |
| TableName tName = getReplacedTableName(getResolver().resolveTable(node.getTableName().getSchemaName(), node.getTableName().getTableName())); |
| return tName == null ? node : TableWildcardParseNode.create(tName, true); |
| } |
| |
| @Override |
| public ParseNode visit(FamilyWildcardParseNode node) throws SQLException { |
| return multiTableRewriteMap != null ? node : new FamilyWildcardParseNode(node, true); |
| } |
| |
| private TableName getReplacedTableName(TableRef origRef) { |
| // if the setTableAlias flag is true and the original table has an alias we use that as the table name |
| if (setTableAlias && origRef.getTableAlias() != null) |
| return TableName.create(null, origRef.getTableAlias()); |
| |
| if (multiTableRewriteMap == null) |
| return null; |
| |
| TableRef tableRef = multiTableRewriteMap.get(origRef); |
| if (tableRef == null) |
| return null; |
| |
| if (origRef.getTableAlias() != null) |
| return TableName.create(null, origRef.getTableAlias()); |
| |
| String schemaName = tableRef.getTable().getSchemaName().getString(); |
| return TableName.create(schemaName.length() == 0 ? null : schemaName, tableRef.getTable().getTableName().getString()); |
| } |
| |
| } |
| |