blob: 8d41132c6b764cad189e50e671828048c68ad370 [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.phoenix.util;
import static org.apache.phoenix.coprocessor.MetaDataProtocol.MIN_SPLITTABLE_SYSTEM_CATALOG;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.LINK_TYPE_BYTES;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.PARENT_TENANT_ID_BYTES;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME_BYTES;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CHILD_LINK_NAME_BYTES;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_FAMILY_BYTES;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_TYPE_BYTES;
import static org.apache.phoenix.schema.PTableImpl.getColumnsToClone;
import static org.apache.phoenix.util.PhoenixRuntime.CURRENT_SCN_ATTRIB;
import static org.apache.phoenix.util.PhoenixRuntime.TENANT_ID_ATTRIB;
import static org.apache.phoenix.util.SchemaUtil.getVarChars;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.Mutation;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
import org.apache.hadoop.hbase.filter.CompareFilter;
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.phoenix.compile.ColumnNameTrackingExpressionCompiler;
import org.apache.phoenix.coprocessor.MetaDataEndpointImpl;
import org.apache.phoenix.coprocessor.MetaDataProtocol;
import org.apache.phoenix.coprocessor.TableInfo;
import org.apache.phoenix.coprocessor.WhereConstantParser;
import org.apache.phoenix.index.IndexMaintainer;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
import org.apache.phoenix.parse.DropTableStatement;
import org.apache.phoenix.parse.ParseNode;
import org.apache.phoenix.parse.SQLParser;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.ColumnNotFoundException;
import org.apache.phoenix.schema.MetaDataClient;
import org.apache.phoenix.schema.PColumn;
import org.apache.phoenix.schema.PColumnImpl;
import org.apache.phoenix.schema.PName;
import org.apache.phoenix.schema.PNameFactory;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTable.LinkType;
import org.apache.phoenix.schema.PTableImpl;
import org.apache.phoenix.schema.PTableType;
import org.apache.phoenix.schema.SaltingUtil;
import org.apache.phoenix.schema.TableNotFoundException;
import org.apache.phoenix.schema.types.PBoolean;
import org.apache.phoenix.schema.types.PLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
public class ViewUtil {
private static final Logger logger = LoggerFactory.getLogger(ViewUtil.class);
/**
* Find all the descendant views of a given table or view in a depth-first fashion.
* Note that apart from scanning the parent->child links, we also validate each view
* by trying to resolve it.
* Use {@link ViewUtil#findAllRelatives(Table, byte[], byte[], byte[], LinkType,
* TableViewFinderResult)} if you want to find other links and don't care about orphan results.
*
* @param sysCatOrsysChildLink Table corresponding to either SYSTEM.CATALOG or SYSTEM.CHILD_LINK
* @param serverSideConfig server-side configuration
* @param tenantId tenantId of the view (null if it is a table or global view)
* @param schemaName schema name of the table/view
* @param tableOrViewName name of the table/view
* @param clientTimeStamp client timestamp
* @param findJustOneLegitimateChildView if true, we are only interested in knowing if there is
* at least one legitimate child view, so we return early.
* If false, we want to find all legitimate child views
* and all orphan views (views that no longer exist)
* stemming from this table/view and all of its legitimate
* child views.
*
* @return a Pair where the first element is a list of all legitimate child views (or just 1
* child view in case findJustOneLegitimateChildView is true) and where the second element is
* a list of all orphan views stemming from this table/view and all of its legitimate child
* views (in case findJustOneLegitimateChildView is true, this list will be incomplete since we
* are not interested in it anyhow)
*
* @throws IOException thrown if there is an error scanning SYSTEM.CHILD_LINK or SYSTEM.CATALOG
* @throws SQLException thrown if there is an error getting a connection to the server or an
* error retrieving the PTable for a child view
*/
public static Pair<List<PTable>, List<TableInfo>> findAllDescendantViews(
Table sysCatOrsysChildLink, Configuration serverSideConfig, byte[] tenantId,
byte[] schemaName, byte[] tableOrViewName, long clientTimeStamp,
boolean findJustOneLegitimateChildView)
throws IOException, SQLException {
List<PTable> legitimateChildViews = new ArrayList<>();
List<TableInfo> orphanChildViews = new ArrayList<>();
findAllDescendantViews(sysCatOrsysChildLink, serverSideConfig, tenantId, schemaName,
tableOrViewName, clientTimeStamp, legitimateChildViews, orphanChildViews,
findJustOneLegitimateChildView);
return new Pair<>(legitimateChildViews, orphanChildViews);
}
private static void findAllDescendantViews(Table sysCatOrsysChildLink,
Configuration serverSideConfig, byte[] parentTenantId, byte[] parentSchemaName,
byte[] parentTableOrViewName, long clientTimeStamp, List<PTable> legitimateChildViews,
List<TableInfo> orphanChildViews, boolean findJustOneLegitimateChildView)
throws IOException, SQLException {
TableViewFinderResult currentResult =
findImmediateRelatedViews(sysCatOrsysChildLink, parentTenantId, parentSchemaName,
parentTableOrViewName, LinkType.CHILD_TABLE, clientTimeStamp);
for (TableInfo viewInfo : currentResult.getLinks()) {
byte[] viewTenantId = viewInfo.getTenantId();
byte[] viewSchemaName = viewInfo.getSchemaName();
byte[] viewName = viewInfo.getTableName();
PTable view;
Properties props = new Properties();
if (viewTenantId != null) {
props.setProperty(TENANT_ID_ATTRIB, Bytes.toString(viewTenantId));
}
if (clientTimeStamp != HConstants.LATEST_TIMESTAMP) {
props.setProperty(CURRENT_SCN_ATTRIB, Long.toString(clientTimeStamp));
}
try (PhoenixConnection connection =
QueryUtil.getConnectionOnServer(props, serverSideConfig)
.unwrap(PhoenixConnection.class)) {
try {
view = PhoenixRuntime.getTableNoCache(connection,
SchemaUtil.getTableName(viewSchemaName, viewName));
} catch (TableNotFoundException ex) {
logger.error("Found an orphan parent->child link keyed by this parent."
+ " Parent Tenant Id: '" + Bytes.toString(parentTenantId)
+ "'. Parent Schema Name: '" + Bytes.toString(parentSchemaName)
+ "'. Parent Table/View Name: '" + Bytes.toString(parentTableOrViewName)
+ "'. The child view which could not be resolved has ViewInfo: '"
+ viewInfo + "'.", ex);
orphanChildViews.add(viewInfo);
// Prune orphan branches
continue;
}
if (isLegitimateChildView(view, parentSchemaName, parentTableOrViewName)) {
legitimateChildViews.add(view);
// return early since we're only interested in knowing if there is at least one
// valid child view
if (findJustOneLegitimateChildView) {
break;
}
// Note that we only explore this branch if the current view is a legitimate
// child view, else we ignore it and move on to the next potential child view
findAllDescendantViews(sysCatOrsysChildLink, serverSideConfig,
viewInfo.getTenantId(), viewInfo.getSchemaName(),
viewInfo.getTableName(), clientTimeStamp, legitimateChildViews,
orphanChildViews, findJustOneLegitimateChildView);
} else {
logger.error("Found an orphan parent->child link keyed by this parent."
+ " Parent Tenant Id: '" + Bytes.toString(parentTenantId)
+ "'. Parent Schema Name: '" + Bytes.toString(parentSchemaName)
+ "'. Parent Table/View Name: '" + Bytes.toString(parentTableOrViewName)
+ "'. There currently exists a legitimate view of the same name which"
+ " is not a descendant of this table/view. View Info: '" + viewInfo
+ "'. Ignoring this view and not counting it as a child view.");
// Prune unrelated view branches left around due to orphan parent->child links
}
}
}
}
private static boolean isLegitimateChildView(PTable view, byte[] parentSchemaName,
byte[] parentTableOrViewName) {
return view != null && view.getParentSchemaName() != null &&
view.getParentTableName() != null &&
(Arrays.equals(view.getParentSchemaName().getBytes(), parentSchemaName) &&
Arrays.equals(view.getParentTableName().getBytes(), parentTableOrViewName));
}
/**
* Returns relatives in a breadth-first fashion. Note that this is not resilient to orphan
* linking rows and we also do not try to resolve any of the views to ensure they are valid.
* Use {@link ViewUtil#findAllDescendantViews(Table, Configuration, byte[], byte[], byte[],
* long, boolean)} if you are only interested in {@link LinkType#CHILD_TABLE} and need to be
* resilient to orphan linking rows.
*
* @param sysCatOrsysChildLink Table corresponding to either SYSTEM.CATALOG or SYSTEM.CHILD_LINK
* @param tenantId tenantId of the key (null if it is a table or global view)
* @param schema schema name to use in the key
* @param table table/view name to use in the key
* @param linkType link type
* @param result containing all linked entities
*
* @throws IOException thrown if there is an error scanning SYSTEM.CHILD_LINK or SYSTEM.CATALOG
*/
public static void findAllRelatives(Table sysCatOrsysChildLink, byte[] tenantId, byte[] schema,
byte[] table, PTable.LinkType linkType, TableViewFinderResult result)
throws IOException {
findAllRelatives(sysCatOrsysChildLink, tenantId, schema, table, linkType,
HConstants.LATEST_TIMESTAMP, result);
}
private static void findAllRelatives(Table sysCatOrsysChildLink, byte[] tenantId, byte[] schema,
byte[] table, PTable.LinkType linkType, long timestamp, TableViewFinderResult result)
throws IOException {
TableViewFinderResult currentResult = findImmediateRelatedViews(sysCatOrsysChildLink,
tenantId, schema, table, linkType, timestamp);
result.addResult(currentResult);
for (TableInfo viewInfo : currentResult.getLinks()) {
findAllRelatives(sysCatOrsysChildLink, viewInfo.getTenantId(), viewInfo.getSchemaName(),
viewInfo.getTableName(), linkType, timestamp, result);
}
}
/**
* Runs a scan on SYSTEM.CATALOG or SYSTEM.CHILD_LINK to get the immediate related tables/views.
*/
private static TableViewFinderResult findImmediateRelatedViews(Table sysCatOrsysChildLink,
byte[] tenantId, byte[] schema, byte[] table, PTable.LinkType linkType, long timestamp)
throws IOException {
if (linkType==PTable.LinkType.INDEX_TABLE || linkType==PTable.LinkType.EXCLUDED_COLUMN) {
throw new IllegalArgumentException("findAllRelatives does not support link type "
+ linkType);
}
byte[] key = SchemaUtil.getTableKey(tenantId, schema, table);
Scan scan = MetaDataUtil.newTableRowsScan(key, MetaDataProtocol.MIN_TABLE_TIMESTAMP,
timestamp);
SingleColumnValueFilter linkFilter = new SingleColumnValueFilter(TABLE_FAMILY_BYTES,
LINK_TYPE_BYTES, CompareFilter.CompareOp.EQUAL,
linkType.getSerializedValueAsByteArray());
linkFilter.setFilterIfMissing(true);
scan.setFilter(linkFilter);
scan.addColumn(TABLE_FAMILY_BYTES, LINK_TYPE_BYTES);
if (linkType==PTable.LinkType.PARENT_TABLE)
scan.addColumn(TABLE_FAMILY_BYTES, PARENT_TENANT_ID_BYTES);
if (linkType==PTable.LinkType.PHYSICAL_TABLE)
scan.addColumn(TABLE_FAMILY_BYTES, TABLE_TYPE_BYTES);
List<TableInfo> tableInfoList = Lists.newArrayList();
try (ResultScanner scanner = sysCatOrsysChildLink.getScanner(scan)) {
for (Result result = scanner.next(); (result != null); result = scanner.next()) {
byte[][] rowKeyMetaData = new byte[5][];
byte[] viewTenantId = null;
getVarChars(result.getRow(), 5, rowKeyMetaData);
if (linkType==PTable.LinkType.PARENT_TABLE) {
viewTenantId = result.getValue(TABLE_FAMILY_BYTES, PARENT_TENANT_ID_BYTES);
} else if (linkType==PTable.LinkType.CHILD_TABLE) {
viewTenantId = rowKeyMetaData[PhoenixDatabaseMetaData.COLUMN_NAME_INDEX];
} else if (linkType==PTable.LinkType.VIEW_INDEX_PARENT_TABLE) {
viewTenantId = rowKeyMetaData[PhoenixDatabaseMetaData.TENANT_ID_INDEX];
}
else if (linkType==PTable.LinkType.PHYSICAL_TABLE &&
result.getValue(TABLE_FAMILY_BYTES, TABLE_TYPE_BYTES)!=null) {
// do not links from indexes to their physical table
continue;
}
byte[] viewSchemaName = SchemaUtil.getSchemaNameFromFullName(
rowKeyMetaData[PhoenixDatabaseMetaData.FAMILY_NAME_INDEX]).getBytes();
byte[] viewName = SchemaUtil.getTableNameFromFullName(
rowKeyMetaData[PhoenixDatabaseMetaData.FAMILY_NAME_INDEX]).getBytes();
tableInfoList.add(new TableInfo(viewTenantId, viewSchemaName, viewName));
}
return new TableViewFinderResult(tableInfoList);
}
}
/**
* Check metadata to find if a given table/view has any immediate child views. Note that this
* is not resilient to orphan parent->child links.
* @param sysCatOrsysChildLink For older (pre-4.15.0) clients, we look for child links inside
* SYSTEM.CATALOG, otherwise we look for them inside
* SYSTEM.CHILD_LINK
* @param tenantId tenantId
* @param schemaName table schema name
* @param tableName table name
* @param timestamp passed client-side timestamp
* @return true if the given table has at least one child view
* @throws IOException thrown if there is an error scanning SYSTEM.CHILD_LINK or SYSTEM.CATALOG
*/
public static boolean hasChildViews(Table sysCatOrsysChildLink, byte[] tenantId,
byte[] schemaName, byte[] tableName, long timestamp) throws IOException {
byte[] key = SchemaUtil.getTableKey(tenantId, schemaName, tableName);
Scan scan = MetaDataUtil.newTableRowsScan(key, MetaDataProtocol.MIN_TABLE_TIMESTAMP,
timestamp);
SingleColumnValueFilter linkFilter =
new SingleColumnValueFilter(TABLE_FAMILY_BYTES, LINK_TYPE_BYTES,
CompareFilter.CompareOp.EQUAL,
LinkType.CHILD_TABLE.getSerializedValueAsByteArray()) {
// if we found a row with the CHILD_TABLE link type we are done and can
// terminate the scan
@Override
public boolean filterAllRemaining() {
return matchedColumn;
}
};
linkFilter.setFilterIfMissing(true);
scan.setFilter(linkFilter);
scan.addColumn(TABLE_FAMILY_BYTES, LINK_TYPE_BYTES);
try (ResultScanner scanner = sysCatOrsysChildLink.getScanner(scan)) {
Result result = scanner.next();
return result!=null;
}
}
/**
* Attempt to drop an orphan child view i.e. a child view for which we see a parent->child entry
* in SYSTEM.CHILD_LINK/SYSTEM.CATALOG (as a child) but for whom the parent no longer exists.
* @param env Region Coprocessor environment
* @param tenantIdBytes tenantId of the parent
* @param schemaName schema of the parent
* @param tableOrViewName parent table/view name
* @param sysCatOrSysChildLink SYSTEM.CATALOG or SYSTEM.CHILD_LINK which is used to find the
* parent->child linking rows
* @throws IOException thrown if there is an error scanning SYSTEM.CHILD_LINK or SYSTEM.CATALOG
* @throws SQLException thrown if there is an error getting a connection to the server or
* an error retrieving the PTable for a child view
*/
public static void dropChildViews(RegionCoprocessorEnvironment env, byte[] tenantIdBytes,
byte[] schemaName, byte[] tableOrViewName, byte[] sysCatOrSysChildLink)
throws IOException, SQLException {
Table hTable = null;
try {
hTable = ServerUtil.getHTableForCoprocessorScan(env, sysCatOrSysChildLink);
} catch (Exception e) {
logger.error("ServerUtil.getHTableForCoprocessorScan error!", e);
}
// if the SYSTEM.CATALOG or SYSTEM.CHILD_LINK doesn't exist just return
if (hTable==null) {
return;
}
TableViewFinderResult childViewsResult;
try {
childViewsResult = findImmediateRelatedViews(
hTable,
tenantIdBytes,
schemaName,
tableOrViewName,
LinkType.CHILD_TABLE,
HConstants.LATEST_TIMESTAMP);
} finally {
hTable.close();
}
for (TableInfo viewInfo : childViewsResult.getLinks()) {
byte[] viewTenantId = viewInfo.getTenantId();
byte[] viewSchemaName = viewInfo.getSchemaName();
byte[] viewName = viewInfo.getTableName();
if (logger.isDebugEnabled()) {
logger.debug("dropChildViews : " + Bytes.toString(schemaName) + "."
+ Bytes.toString(tableOrViewName) + " -> "
+ Bytes.toString(viewSchemaName) + "." + Bytes.toString(viewName)
+ "with tenant id :" + Bytes.toString(viewTenantId));
}
Properties props = new Properties();
PTable view = null;
if (viewTenantId != null && viewTenantId.length != 0)
props.setProperty(TENANT_ID_ATTRIB, Bytes.toString(viewTenantId));
try (PhoenixConnection connection = QueryUtil.getConnectionOnServer(props,
env.getConfiguration()).unwrap(PhoenixConnection.class)) {
try {
// Ensure that the view to be dropped has some ancestor that no longer exists
// (and thus will throw a TableNotFoundException). Otherwise, if we are looking
// at an orphan parent->child link, then the view might actually be a legitimate
// child view on another table/view and we should obviously not drop it
view = PhoenixRuntime.getTableNoCache(connection,
SchemaUtil.getTableName(viewSchemaName, viewName));
} catch (TableNotFoundException expected) {
// Expected for an orphan view since some ancestor was dropped earlier
logger.info("Found an expected orphan parent->child link keyed by the parent."
+ " Parent Tenant Id: '" + Bytes.toString(tenantIdBytes)
+ "'. Parent Schema Name: '" + Bytes.toString(schemaName)
+ "'. Parent Table/View Name: '" + Bytes.toString(tableOrViewName)
+ "'. Will attempt to drop this child view with ViewInfo: '"
+ viewInfo + "'.");
}
if (view != null) {
logger.error("Found an orphan parent->child link keyed by this parent or"
+ " its descendant. Parent Tenant Id: '" + Bytes.toString(tenantIdBytes)
+ "'. Parent Schema Name: '" + Bytes.toString(schemaName)
+ "'. Parent Table/View Name: '" + Bytes.toString(tableOrViewName)
+ "'. There currently exists a legitimate view of the same name whose"
+ " parent hierarchy exists. View Info: '" + viewInfo
+ "'. Ignoring this view and not attempting to drop it.");
continue;
}
MetaDataClient client = new MetaDataClient(connection);
org.apache.phoenix.parse.TableName viewTableName =
org.apache.phoenix.parse.TableName.create(Bytes.toString(viewSchemaName),
Bytes.toString(viewName));
try {
client.dropTable(new DropTableStatement(viewTableName, PTableType.VIEW, true,
true, true));
} catch (TableNotFoundException e) {
logger.info("Ignoring view " + viewTableName
+ " as it has already been dropped");
}
}
}
}
/**
* Determines whether we should use SYSTEM.CATALOG or SYSTEM.CHILD_LINK to find parent->child
* links i.e. {@link LinkType#CHILD_TABLE}.
* If the client is older than 4.15.0 and the SYSTEM.CHILD_LINK table does not exist, we use
* the SYSTEM.CATALOG table. In all other cases, we use the SYSTEM.CHILD_LINK table.
* This is required for backwards compatibility.
* @param clientVersion client version
* @param conf server-side configuration
* @return name of the system table to be used
* @throws SQLException thrown if there is an error connecting to the server
*/
public static TableName getSystemTableForChildLinks(int clientVersion,
Configuration conf) throws SQLException, IOException {
byte[] fullTableName = SYSTEM_CHILD_LINK_NAME_BYTES;
if (clientVersion < MIN_SPLITTABLE_SYSTEM_CATALOG) {
try (PhoenixConnection connection = QueryUtil.getConnectionOnServer(
conf).unwrap(PhoenixConnection.class);
HBaseAdmin admin = connection.getQueryServices().getAdmin()) {
// If this is an old client and the CHILD_LINK table doesn't exist i.e. metadata
// hasn't been updated since there was never a connection from a 4.15 client
if (!admin.tableExists(SchemaUtil.getPhysicalTableName(
SYSTEM_CHILD_LINK_NAME_BYTES, conf))) {
fullTableName = SYSTEM_CATALOG_NAME_BYTES;
}
} catch (SQLException e) {
logger.error("Error getting a connection on the server : " + e);
throw e;
}
}
return SchemaUtil.getPhysicalTableName(fullTableName, conf);
}
public static boolean isDivergedView(PTable view) {
return view.getBaseColumnCount() == QueryConstants.DIVERGED_VIEW_BASE_COLUMN_COUNT;
}
public static boolean isViewDiverging(PColumn columnToDelete, PTable view,
long clientVersion) {
// If we are dropping a column from a pre-4.15 client, the only way to know if the
// view is diverging is by comparing the base column count
return !isDivergedView(view) && (clientVersion < MIN_SPLITTABLE_SYSTEM_CATALOG ?
columnToDelete.getPosition() < view.getBaseColumnCount() :
columnToDelete.isDerived());
}
/**
* Adds indexes of the parent table to inheritedIndexes if the index contains all required
* columns
*/
public static void addIndexesFromParent(PhoenixConnection connection, PTable view,
PTable parentTable, List<PTable> inheritedIndexes) throws SQLException {
List<PTable> parentTableIndexes = parentTable.getIndexes();
for (PTable index : parentTableIndexes) {
boolean containsAllReqdCols = true;
// Ensure that all columns required to create index exist in the view too,
// since view columns may be removed.
IndexMaintainer indexMaintainer = index.getIndexMaintainer(parentTable, connection);
// Check that the columns required for the index pk are present in the view
Set<Pair<String, String>> indexedColInfos = indexMaintainer.getIndexedColumnInfo();
for (Pair<String, String> colInfo : indexedColInfos) {
try {
String colFamily = colInfo.getFirst();
String colName = colInfo.getSecond();
if (colFamily == null) {
view.getColumnForColumnName(colName);
} else {
view.getColumnFamily(colFamily).getPColumnForColumnName(colName);
}
} catch (ColumnNotFoundException e) {
containsAllReqdCols = false;
break;
}
}
// Ensure that constant columns (i.e. columns matched in the view WHERE clause)
// all exist in the index on the parent table.
for (PColumn col : view.getColumns()) {
if (col.isViewReferenced() || col.getViewConstant() != null) {
try {
// It'd be possible to use a local index that doesn't have all view
// constants, but the WHERE clause for the view statement (which is added to
// the index below) would fail to compile.
String indexColumnName = IndexUtil.getIndexColumnName(col);
index.getColumnForColumnName(indexColumnName);
} catch (ColumnNotFoundException e1) {
PColumn indexCol = null;
try {
String cf = col.getFamilyName()!=null ?
col.getFamilyName().getString() : null;
String colName = col.getName().getString();
if (cf != null) {
indexCol = parentTable.getColumnFamily(cf)
.getPColumnForColumnName(colName);
}
else {
indexCol = parentTable.getColumnForColumnName(colName);
}
} catch (ColumnNotFoundException e2) {
// Ignore this index and continue with others
containsAllReqdCols = false;
break;
}
if (indexCol.getViewConstant()==null || Bytes.compareTo(
indexCol.getViewConstant(), col.getViewConstant())!=0) {
containsAllReqdCols = false;
break;
}
}
}
}
if (containsAllReqdCols) {
// Tack on view statement to index to get proper filtering for view
String viewStatement = IndexUtil.rewriteViewStatement(connection, index,
parentTable, view.getViewStatement());
PName modifiedIndexName = PNameFactory.newName(view.getName().getString()
+ QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR +
index.getName().getString());
// add the index table with a new name so that it does not conflict with the
// existing index table and set update cache frequency to that of the view
if (Objects.equal(viewStatement, index.getViewStatement())) {
inheritedIndexes.add(index);
} else {
inheritedIndexes.add(PTableImpl.builderWithColumns(index,
getColumnsToClone(index))
.setTableName(modifiedIndexName)
.setViewStatement(viewStatement)
.setUpdateCacheFrequency(view.getUpdateCacheFrequency())
.setTenantId(view.getTenantId())
.setPhysicalNames(Collections.singletonList(index.getPhysicalName()))
.build());
}
}
}
}
/**
* Inherit all indexes and columns from the parent
* @return table with inherited columns and indexes
*/
public static PTable addDerivedColumnsAndIndexesFromParent(PhoenixConnection connection,
PTable table, PTable parentTable) throws SQLException {
PTable pTable = addDerivedColumnsFromParent(table, parentTable);
boolean hasIndexId = table.getViewIndexId() != null;
// For views :
if (!hasIndexId) {
// 1. need to resolve the views's own indexes so that any columns added by ancestors
// are included
List<PTable> allIndexes = Lists.newArrayList();
if (pTable !=null && pTable.getIndexes() !=null && !pTable.getIndexes().isEmpty()) {
for (PTable viewIndex : pTable.getIndexes()) {
PTable resolvedViewIndex = ViewUtil.addDerivedColumnsAndIndexesFromParent(
connection, viewIndex, pTable);
if (resolvedViewIndex!=null)
allIndexes.add(resolvedViewIndex);
}
}
// 2. include any indexes from ancestors that can be used by this view
List<PTable> inheritedIndexes = Lists.newArrayList();
addIndexesFromParent(connection, pTable, parentTable, inheritedIndexes);
allIndexes.addAll(inheritedIndexes);
if (!allIndexes.isEmpty()) {
pTable = PTableImpl.builderWithColumns(pTable, getColumnsToClone(pTable))
.setIndexes(allIndexes).build();
}
}
return pTable;
}
/**
* Inherit all columns from the parent unless it's an excluded column.
* If the same column is present in the parent and child (for table metadata created before
* PHOENIX-3534) we choose the child column over the parent column
* @return table with inherited columns
*/
public static PTable addDerivedColumnsFromParent(PTable view, PTable parentTable)
throws SQLException {
// combine columns for view and view indexes
boolean hasIndexId = view.getViewIndexId() != null;
boolean isSalted = view.getBucketNum() != null;
boolean isDiverged = isDivergedView(view);
boolean isDivergedViewCreatedPre4_15 = isDiverged;
List<PColumn> allColumns = Lists.newArrayList();
List<PColumn> excludedColumns = Lists.newArrayList();
// add my own columns first in reverse order
List<PColumn> myColumns = view.getColumns();
// skip salted column as it will be created automatically
myColumns = myColumns.subList(isSalted ? 1 : 0, myColumns.size());
for (int i = myColumns.size() - 1; i >= 0; i--) {
PColumn pColumn = myColumns.get(i);
if (pColumn.isExcluded()) {
// Diverged views created pre-4.15 will not have EXCLUDED_COLUMN linking rows
isDivergedViewCreatedPre4_15 = false;
excludedColumns.add(pColumn);
}
allColumns.add(pColumn);
}
// initialize map from with indexed expression to list of required data columns
// then remove the data columns that have not been dropped, so that we get the columns that
// have been dropped
Map<PColumn, List<String>> indexRequiredDroppedDataColMap =
Maps.newHashMapWithExpectedSize(view.getColumns().size());
if (hasIndexId) {
int indexPosOffset = (isSalted ? 1 : 0) + (view.isMultiTenant() ? 1 : 0) + 1;
ColumnNameTrackingExpressionCompiler expressionCompiler =
new ColumnNameTrackingExpressionCompiler();
for (int i = indexPosOffset; i < view.getPKColumns().size(); i++) {
PColumn indexColumn = view.getPKColumns().get(i);
try {
expressionCompiler.reset();
String expressionStr = IndexUtil.getIndexColumnExpressionStr(indexColumn);
ParseNode parseNode = SQLParser.parseCondition(expressionStr);
parseNode.accept(expressionCompiler);
indexRequiredDroppedDataColMap.put(indexColumn,
Lists.newArrayList(expressionCompiler.getDataColumnNames()));
} catch (SQLException e) {
throw new RuntimeException(e); // Impossible
}
}
}
long maxTableTimestamp = view.getTimeStamp();
int numPKCols = view.getPKColumns().size();
// set the final table timestamp as the max timestamp of the view/view index or its
// ancestors
maxTableTimestamp = Math.max(maxTableTimestamp, parentTable.getTimeStamp());
if (hasIndexId) {
// add all pk columns of parent tables to indexes
// skip salted column as it will be added from the base table columns
int startIndex = parentTable.getBucketNum() != null ? 1 : 0;
for (int index=startIndex; index<parentTable.getPKColumns().size(); index++) {
PColumn pkColumn = parentTable.getPKColumns().get(index);
// don't add the salt column of ancestor tables for view indexes, or deleted columns
// or constant columns from the view where statement
if (pkColumn.equals(SaltingUtil.SALTING_COLUMN) || pkColumn.isExcluded()
|| pkColumn.getViewConstant()!=null) {
continue;
}
pkColumn = IndexUtil.getIndexPKColumn(++numPKCols, pkColumn);
int existingColumnIndex = allColumns.indexOf(pkColumn);
if (existingColumnIndex == -1) {
allColumns.add(0, pkColumn);
}
}
for (int j = 0; j < parentTable.getColumns().size(); j++) {
PColumn tableColumn = parentTable.getColumns().get(j);
if (tableColumn.isExcluded()) {
continue;
}
String dataColumnName = tableColumn.getName().getString();
// remove from list of columns since it has not been dropped
for (Map.Entry<PColumn, List<String>> entry : indexRequiredDroppedDataColMap
.entrySet()) {
entry.getValue().remove(dataColumnName);
}
}
} else if (!isDivergedViewCreatedPre4_15) {
// For diverged views created by a pre-4.15 client, we don't need to inherit columns
// from its ancestors
inheritColumnsFromParent(view, parentTable, isDiverged, excludedColumns, allColumns);
}
// at this point indexRequiredDroppedDataColMap only contain the columns required by a view
// index that have dropped
for (Map.Entry<PColumn, List<String>> entry : indexRequiredDroppedDataColMap.entrySet()) {
if (!entry.getValue().isEmpty()) {
PColumn indexColumnToBeDropped = entry.getKey();
if (SchemaUtil.isPKColumn(indexColumnToBeDropped)) {
// if an indexed column was dropped in an ancestor then we
// cannot use this index an more
// TODO figure out a way to actually drop this view index
return null;
} else {
allColumns.remove(indexColumnToBeDropped);
}
}
}
List<PColumn> columnsToAdd = Lists.newArrayList();
int position = isSalted ? 1 : 0;
// allColumns contains the columns in the reverse order
for (int i = allColumns.size() - 1; i >= 0; i--) {
PColumn column = allColumns.get(i);
if (view.getColumns().contains(column)) {
// for views this column is not derived from an ancestor
columnsToAdd.add(new PColumnImpl(column, position++));
} else {
columnsToAdd.add(new PColumnImpl(column, true, position++));
}
}
// we need to include the salt column when setting the base table column count in order to
// maintain b/w compatibility
int baseTableColumnCount =
isDiverged ? QueryConstants.DIVERGED_VIEW_BASE_COLUMN_COUNT
: columnsToAdd.size() - myColumns.size() + (isSalted ? 1 : 0);
// Inherit view-modifiable properties from the parent table/view if the current view has
// not previously modified this property
long updateCacheFreq = (view.getType() != PTableType.VIEW ||
view.hasViewModifiedUpdateCacheFrequency()) ?
view.getUpdateCacheFrequency() : parentTable.getUpdateCacheFrequency();
Boolean useStatsForParallelization = (view.getType() != PTableType.VIEW ||
view.hasViewModifiedUseStatsForParallelization()) ?
view.useStatsForParallelization() : parentTable.useStatsForParallelization();
// When creating a PTable for views or view indexes, use the baseTable PTable for attributes
// inherited from the physical base table.
// if a TableProperty is not valid on a view we set it to the base table value
// if a TableProperty is valid on a view and is not mutable on a view we set it to the base
// table value
// if a TableProperty is valid on a view and is mutable on a view, we use the value set
// on the view if the view had previously modified the property, otherwise we propagate the
// value from the base table (see PHOENIX-4763)
PTable pTable = PTableImpl.builderWithColumns(view, columnsToAdd)
.setImmutableRows(parentTable.isImmutableRows())
.setDisableWAL(parentTable.isWALDisabled())
.setMultiTenant(parentTable.isMultiTenant())
.setStoreNulls(parentTable.getStoreNulls())
.setTransactionProvider(parentTable.getTransactionProvider())
.setAutoPartitionSeqName(parentTable.getAutoPartitionSeqName())
.setAppendOnlySchema(parentTable.isAppendOnlySchema())
.setBaseColumnCount(baseTableColumnCount)
.setTimeStamp(maxTableTimestamp)
.setExcludedColumns(ImmutableList.copyOf(excludedColumns))
.setUpdateCacheFrequency(updateCacheFreq)
.setUseStatsForParallelization(useStatsForParallelization)
.build();
pTable = WhereConstantParser.addViewInfoToPColumnsIfNeeded(pTable);
return pTable;
}
/**
* Inherit all columns from the parent unless it's an excluded column.
* If the same column is present in the parent and child
* (for table metadata created before PHOENIX-3534 or when
* {@link org.apache.phoenix.query.QueryServices#ALLOW_SPLITTABLE_SYSTEM_CATALOG_ROLLBACK} is
* enabled) we choose the latest column.
* Note that we don't need to call this method for views created before 4.15 since they
* already contain all the columns from their ancestors.
* @param view PTable of the view
* @param parentTable PTable of the view's parent
* @param isDiverged true if it is a diverged view
* @param excludedColumns list of excluded columns
* @param allColumns list of all columns. Initially this contains just the columns in the view.
* We will populate inherited columns by adding them to this list
*/
static void inheritColumnsFromParent(PTable view, PTable parentTable,
boolean isDiverged, List<PColumn> excludedColumns, List<PColumn> allColumns) {
List<PColumn> currAncestorTableCols = PTableImpl.getColumnsToClone(parentTable);
if (currAncestorTableCols != null) {
// add the ancestor columns in reverse order so that the final column list
// (reversed outside of this method invocation)
// contains ancestor columns and then the view columns in the right order
for (int j = currAncestorTableCols.size() - 1; j >= 0; j--) {
PColumn ancestorColumn = currAncestorTableCols.get(j);
// for diverged views we always include pk columns of the base table. We
// have to include these pk columns to be able to support adding pk
// columns to the diverged view.
// We only include regular columns that were created before the view
// diverged.
if (isDiverged && ancestorColumn.getFamilyName() != null
&& ancestorColumn.getTimestamp() > view.getTimeStamp()) {
// If this is a diverged view, the ancestor column is not a PK and the ancestor
// column was added after the view diverged, ignore this ancestor column.
continue;
}
// need to check if this ancestor column is in the list of excluded (dropped)
// columns of the view
int existingExcludedIndex = excludedColumns.indexOf(ancestorColumn);
if (existingExcludedIndex != -1) {
// if it is, only exclude the ancestor column if it was created before the
// column was dropped in the view in order to handle the case where
// a base table column is dropped in a view, then dropped in the
// base table and then added back to the base table
if (ancestorColumn.getTimestamp() <= excludedColumns.get(existingExcludedIndex)
.getTimestamp()) {
continue;
}
}
// A diverged view from a pre-4.15 client won't ever go in this case since
// isExcluded was introduced in 4.15. If this is a 4.15+ client, excluded columns
// will be identifiable via PColumn#isExcluded()
if (ancestorColumn.isExcluded()) {
excludedColumns.add(ancestorColumn);
} else {
int existingColumnIndex = allColumns.indexOf(ancestorColumn);
if (existingColumnIndex != -1) {
// For non-diverged views, if the same column exists in a parent and child,
// we keep the latest column.
PColumn existingColumn = allColumns.get(existingColumnIndex);
if (!isDiverged && ancestorColumn.getTimestamp() >
existingColumn.getTimestamp()) {
allColumns.remove(existingColumnIndex);
// Remove the existing column and add the ancestor
// column at the end and make sure to mark it
// as derived
allColumns.add(new PColumnImpl(ancestorColumn, true,
ancestorColumn.getPosition()));
} else {
// Since this is a column from the ancestor,
// mark it as derived
allColumns.set(existingColumnIndex,
new PColumnImpl(existingColumn, true,
existingColumn.getPosition()));
}
} else {
// Since this is a column from the ancestor,
// mark it as derived
allColumns.add(new PColumnImpl(ancestorColumn, true,
ancestorColumn.getPosition()));
}
}
}
}
// remove the excluded columns if the timestamp of the excludedColumn is newer
for (PColumn excludedColumn : excludedColumns) {
int index = allColumns.indexOf(excludedColumn);
if (index != -1) {
if (allColumns.get(index).getTimestamp() <= excludedColumn.getTimestamp()) {
allColumns.remove(excludedColumn);
}
}
}
}
/**
* See PHOENIX-4763. If we are modifying any table-level properties that are mutable on a view,
* we mark these cells in SYSTEM.CATALOG with tags to indicate that this view property should
* not be kept in-sync with the base table and so we shouldn't propagate the base table's
* property value when resolving the view
* @param tableMetaData list of mutations on the view
* @param parent PTable of the parent or null
*/
public static void addTagsToPutsForViewAlteredProperties(List<Mutation> tableMetaData,
PTable parent) {
byte[] parentUpdateCacheFreqBytes = null;
byte[] parentUseStatsForParallelizationBytes = null;
byte[] parentPhoenixTTLBytes = null;
if (parent != null) {
parentUpdateCacheFreqBytes = new byte[PLong.INSTANCE.getByteSize()];
PLong.INSTANCE.getCodec().encodeLong(parent.getUpdateCacheFrequency(),
parentUpdateCacheFreqBytes, 0);
if (parent.useStatsForParallelization() != null) {
parentUseStatsForParallelizationBytes =
PBoolean.INSTANCE.toBytes(parent.useStatsForParallelization());
}
parentPhoenixTTLBytes = new byte[PLong.INSTANCE.getByteSize()];
PLong.INSTANCE.getCodec().encodeLong(parent.getPhoenixTTL(),
parentPhoenixTTLBytes, 0);
}
for (Mutation m: tableMetaData) {
if (m instanceof Put) {
MetaDataUtil.conditionallyAddTagsToPutCells((Put)m,
PhoenixDatabaseMetaData.TABLE_FAMILY_BYTES,
PhoenixDatabaseMetaData.UPDATE_CACHE_FREQUENCY_BYTES,
parentUpdateCacheFreqBytes,
MetaDataEndpointImpl.VIEW_MODIFIED_PROPERTY_BYTES);
MetaDataUtil.conditionallyAddTagsToPutCells((Put)m,
PhoenixDatabaseMetaData.TABLE_FAMILY_BYTES,
PhoenixDatabaseMetaData.USE_STATS_FOR_PARALLELIZATION_BYTES,
parentUseStatsForParallelizationBytes,
MetaDataEndpointImpl.VIEW_MODIFIED_PROPERTY_BYTES);
MetaDataUtil.conditionallyAddTagsToPutCells((Put)m,
PhoenixDatabaseMetaData.TABLE_FAMILY_BYTES,
PhoenixDatabaseMetaData.PHOENIX_TTL_BYTES,
parentPhoenixTTLBytes,
MetaDataEndpointImpl.VIEW_MODIFIED_PROPERTY_BYTES);
}
}
}
}