blob: dbbb0032171491a5d0dbd5d8c38599e259fc2ae6 [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.mapreduce;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.COLUMN_FAMILY;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.COLUMN_NAME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.LINK_TYPE;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CHILD_LINK_NAME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_NAME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_SCHEM;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_TYPE;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TENANT_ID;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.VIEW_TYPE;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.mapreduce.util.ConnectionUtil;
import org.apache.phoenix.parse.DropTableStatement;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.query.QueryServicesOptions;
import org.apache.phoenix.schema.MetaDataClient;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTableType;
import org.apache.phoenix.schema.TableNotFoundException;
import org.apache.phoenix.util.EnvironmentEdgeManager;
import org.apache.phoenix.util.PhoenixRuntime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A tool to identify orphan views and links, and drop them
*
*/
public class OrphanViewTool extends Configured implements Tool {
private static final Logger LOGGER = LoggerFactory.getLogger(OrphanViewTool.class);
// Query all the views that are not "MAPPED" views
private static final String viewQuery = "SELECT " +
TENANT_ID + ", " +
TABLE_SCHEM + "," +
TABLE_NAME +
" FROM " + SYSTEM_CATALOG_NAME +
" WHERE "+ TABLE_TYPE + " = '" + PTableType.VIEW.getSerializedValue() +"' AND NOT " +
VIEW_TYPE + " = " + PTable.ViewType.MAPPED.getSerializedValue();
// Query all physical links
private static final String physicalLinkQuery = "SELECT " +
TENANT_ID + ", " +
TABLE_SCHEM + ", " +
TABLE_NAME + ", " +
COLUMN_NAME + " AS PHYSICAL_TABLE_TENANT_ID, " +
COLUMN_FAMILY + " AS PHYSICAL_TABLE_FULL_NAME " +
" FROM " + SYSTEM_CATALOG_NAME +
" WHERE "+ LINK_TYPE + " = " +
PTable.LinkType.PHYSICAL_TABLE.getSerializedValue();
// Query all child-parent links
private static final String childParentLinkQuery = "SELECT " +
TENANT_ID + ", " +
TABLE_SCHEM + ", " +
TABLE_NAME + ", " +
COLUMN_NAME + " AS PARENT_VIEW_TENANT_ID, " +
COLUMN_FAMILY + " AS PARENT_VIEW_FULL_NAME " +
" FROM " + SYSTEM_CATALOG_NAME +
" WHERE "+ LINK_TYPE + " = " +
PTable.LinkType.PARENT_TABLE.getSerializedValue();
// Query all parent-child links
private static final String parentChildLinkQuery = "SELECT " +
TENANT_ID + ", " +
TABLE_SCHEM + ", " +
TABLE_NAME + ", " +
COLUMN_NAME + " AS CHILD_VIEW_TENANT_ID, " +
COLUMN_FAMILY + " AS CHILD_VIEW_FULL_NAME " +
" FROM " + SYSTEM_CHILD_LINK_NAME +
" WHERE "+ LINK_TYPE + " = " +
PTable.LinkType.CHILD_TABLE.getSerializedValue();
// Query all the tables that can be a base table
private static final String candidateBaseTableQuery = "SELECT " +
TENANT_ID + ", " +
TABLE_SCHEM + ", " +
TABLE_NAME +
" FROM " + SYSTEM_CATALOG_NAME +
" WHERE " + TABLE_TYPE + " != '" + PTableType.VIEW.getSerializedValue() + "' AND " +
TABLE_TYPE + " != '" + PTableType.INDEX.getSerializedValue() + "'";
// The path of the directory of the output files
private String outputPath;
// The path of the directory of the input files
private String inputPath;
// The flag to indicate if the orphan views and links will be deleted
private boolean clean = false;
// The maximum level found in a view tree
private int maxViewLevel = 0;
// The age of a view
private static final long defaultAgeMs = 24*60*60*1000; // 1 day
private long ageMs = 0;
// A separate file is maintained to list orphan views, and each type of orphan links
public static final byte VIEW = 0;
public static final byte PHYSICAL_TABLE_LINK = 1;
public static final byte PARENT_TABLE_LINK = 2;
public static final byte CHILD_TABLE_LINK = 3;
public static final byte ORPHAN_TYPE_COUNT = 4;
BufferedWriter writer[] = new BufferedWriter[ORPHAN_TYPE_COUNT];
BufferedReader reader[] = new BufferedReader[ORPHAN_TYPE_COUNT];
// The set of orphan views
HashMap<Key, View> orphanViewSet = new HashMap<>();
// The array list of set of views such that the views in the first set are the first level views and the views
// in the second set is the second level views, and so on
List<HashMap<Key, View>> viewSetArray = new ArrayList<HashMap<Key, View>>();
// The set of base tables
HashMap<Key, Base> baseSet = new HashMap<>();
// The set of orphan links. These links can be CHILD_TABLE, PARENT_TABLE, or PHYSICAL_TABLE links
HashSet<Link> orphanLinkSet = new HashSet<>();
public static final String fileName[] = {"OrphanView.txt", "OrphanPhysicalTableLink.txt", "OrphanParentTableLink.txt", "OrphanChildTableLink.txt"};
private static final Option OUTPUT_PATH_OPTION = new Option("op", "output-path", true,
"Output path where the files listing orphan views and links are written");
private static final Option INPUT_PATH_OPTION = new Option("ip", "input-path", true,
"Input path where the files listing orphan views and links are read");
private static final Option CLEAN_ORPHAN_VIEWS_OPTION = new Option("c", "clean", false,
"If specified, cleans orphan views and links");
private static final Option IDENTIFY_ORPHAN_VIEWS_OPTION = new Option("i", "identify", false,
"If specified, identifies orphan views and links");
private static final Option AGE_OPTION = new Option("a", "age", true,
"The minimum age (in milliseconds) for the views (default value is " + Long.toString(defaultAgeMs) + ", i.e. 1 day)");
private static final Option HELP_OPTION = new Option("h", "help", false, "Help");
private Options getOptions() {
final Options options = new Options();
options.addOption(OUTPUT_PATH_OPTION);
options.addOption(INPUT_PATH_OPTION);
options.addOption(CLEAN_ORPHAN_VIEWS_OPTION);
options.addOption(IDENTIFY_ORPHAN_VIEWS_OPTION);
options.addOption(AGE_OPTION);
options.addOption(HELP_OPTION);
return options;
}
/**
* Parses the commandline arguments, throws IllegalStateException if mandatory arguments are
* missing.
* @param args supplied command line arguments
*/
private void parseOptions(String[] args) throws Exception {
final Options options = getOptions();
CommandLineParser parser = new PosixParser();
CommandLine cmdLine = null;
try {
cmdLine = parser.parse(options, args);
} catch (ParseException e) {
printHelpAndExit("Error parsing command line options: " + e.getMessage(), options);
}
if (cmdLine.hasOption(HELP_OPTION.getOpt())) {
printHelpAndExit(options, 0);
}
if (cmdLine.hasOption(OUTPUT_PATH_OPTION.getOpt()) && cmdLine.hasOption(INPUT_PATH_OPTION.getOpt())) {
throw new IllegalStateException("Specify either " + OUTPUT_PATH_OPTION.getLongOpt() + " or "
+ INPUT_PATH_OPTION.getOpt());
}
if (cmdLine.hasOption(INPUT_PATH_OPTION.getOpt()) && !cmdLine.hasOption(CLEAN_ORPHAN_VIEWS_OPTION.getOpt())) {
throw new IllegalStateException(INPUT_PATH_OPTION.getLongOpt() + " is only used with "
+ IDENTIFY_ORPHAN_VIEWS_OPTION.getOpt());
}
if (cmdLine.hasOption(IDENTIFY_ORPHAN_VIEWS_OPTION.getOpt()) && cmdLine.hasOption(CLEAN_ORPHAN_VIEWS_OPTION.getOpt())) {
throw new IllegalStateException("Specify either " + IDENTIFY_ORPHAN_VIEWS_OPTION.getLongOpt() + " or "
+ IDENTIFY_ORPHAN_VIEWS_OPTION.getOpt());
}
if (cmdLine.hasOption(OUTPUT_PATH_OPTION.getOpt()) && (!cmdLine.hasOption(IDENTIFY_ORPHAN_VIEWS_OPTION.getOpt()) &&
!cmdLine.hasOption(CLEAN_ORPHAN_VIEWS_OPTION.getOpt()))) {
throw new IllegalStateException(OUTPUT_PATH_OPTION.getLongOpt() + " requires either " +
IDENTIFY_ORPHAN_VIEWS_OPTION.getOpt() + " or " + CLEAN_ORPHAN_VIEWS_OPTION.getOpt());
}
if (cmdLine.hasOption(CLEAN_ORPHAN_VIEWS_OPTION.getOpt())) {
clean = true;
}
else if (!cmdLine.hasOption(IDENTIFY_ORPHAN_VIEWS_OPTION.getOpt())) {
throw new IllegalStateException("Specify either " +
IDENTIFY_ORPHAN_VIEWS_OPTION.getOpt() + " or " + CLEAN_ORPHAN_VIEWS_OPTION.getOpt());
}
if (cmdLine.hasOption(AGE_OPTION.getOpt())) {
ageMs = Long.valueOf(cmdLine.getOptionValue(AGE_OPTION.getOpt()));
}
outputPath = cmdLine.getOptionValue(OUTPUT_PATH_OPTION.getOpt());
inputPath = cmdLine.getOptionValue(INPUT_PATH_OPTION.getOpt());
}
private void printHelpAndExit(String errorMessage, Options options) {
System.err.println(errorMessage);
printHelpAndExit(options, 1);
}
private void printHelpAndExit(Options options, int exitCode) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("help", options);
System.exit(exitCode);
}
/**
* The key that uniquely identifies a table (i.e., a base table or table view)
*/
private static class Key {
private String serializedValue;
public Key (String tenantId, String schemaName, String tableName) throws IllegalArgumentException {
if (tableName == null) {
throw new IllegalArgumentException();
}
serializedValue = (tenantId != null ? tenantId + "," : ",") +
(schemaName != null ? schemaName + "," : ",") +
tableName;
}
public Key (String tenantId, String fullTableName) {
String[] columns = fullTableName.split("\\.");
String schemaName;
String tableName;
if (columns.length == 1) {
schemaName = null;
tableName = fullTableName;
} else {
schemaName = columns[0];
tableName = columns[1];
}
if (tableName == null || tableName.compareTo("") == 0) {
throw new IllegalArgumentException();
}
serializedValue = (tenantId != null ? tenantId + "," : ",") +
(schemaName != null ? schemaName + "," : ",") +
tableName;
}
public Key (String serializedKey) {
serializedValue = serializedKey;
if (this.getTableName() == null || this.getTableName().compareTo("") == 0) {
throw new IllegalArgumentException();
}
}
public String getTenantId() {
String[] columns = serializedValue.split(",");
return columns[0].compareTo("") == 0 ? null : columns[0];
}
public String getSchemaName() {
String[] columns = serializedValue.split(",");
return columns[1].compareTo("") == 0 ? null : columns[1];
}
public String getTableName() {
String[] columns = serializedValue.split(",");
return columns[2];
}
public String getSerializedValue() {
return serializedValue;
}
@Override
public int hashCode() {
return Objects.hash(getSerializedValue());
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (getClass() != obj.getClass())
return false;
Key other = (Key) obj;
if (this.getSerializedValue().compareTo(other.getSerializedValue()) != 0)
return false;
return true;
}
}
/**
* An abstract class to represent a table that can be a base table or table view
*/
private static abstract class Table {
protected Key key;
protected List<Key> childViews;
public void addChild(Key childView) {
if (childViews == null) {
childViews = new LinkedList<>();
}
childViews.add(childView);
}
public boolean isParent() {
if (childViews == null || childViews.isEmpty()) {
return false;
}
return true;
}
}
/**
* A class to represents a base table
*/
private static class Base extends Table {
public Base (Key key) {
this.key = key;
}
}
/**
* A class to represents a table view
*/
private static class View extends Table {
Key parent;
Key base;
public View (Key key) {
this.key = key;
}
public void setParent(Key parent) {
this.parent = parent;
}
public void setBase(Key base) {
this.base = base;
}
}
private static class Link {
Key src;
Key dst;
PTable.LinkType type;
public Link(Key src, Key dst, PTable.LinkType type) {
this.src = src;
this.dst = dst;
this.type = type;
}
public String serialize() {
return src.getSerializedValue() + "," + dst.getSerializedValue() + "," + type.toString();
}
@Override
public int hashCode() {
return Objects.hash(serialize());
}
}
private void gracefullyDropView(PhoenixConnection phoenixConnection,
Configuration configuration, Key key) throws Exception {
PhoenixConnection tenantConnection = null;
boolean newConn = false;
try {
if (key.getTenantId() != null) {
Properties tenantProps = new Properties();
tenantProps.setProperty(PhoenixRuntime.TENANT_ID_ATTRIB, key.getTenantId());
tenantConnection = ConnectionUtil.getInputConnection(configuration, tenantProps).
unwrap(PhoenixConnection.class);
newConn = true;
} else {
tenantConnection = phoenixConnection;
}
MetaDataClient client = new MetaDataClient(tenantConnection);
org.apache.phoenix.parse.TableName pTableName = org.apache.phoenix.parse.TableName
.create(key.getSchemaName(), key.getTableName());
try {
client.dropTable(
new DropTableStatement(pTableName, PTableType.VIEW, false, true, true));
}
catch (TableNotFoundException e) {
LOGGER.info("Ignoring view " + pTableName + " as it has already been dropped");
}
} finally {
if (newConn) {
tryClosingConnection(tenantConnection);
}
}
}
private void removeLink(PhoenixConnection phoenixConnection, Key src, Key dst, PTable.LinkType linkType) throws Exception {
String deleteQuery = "DELETE FROM " +
((linkType == PTable.LinkType.PHYSICAL_TABLE || linkType == PTable.LinkType.PARENT_TABLE) ? SYSTEM_CATALOG_NAME : SYSTEM_CHILD_LINK_NAME) +
" WHERE " + TENANT_ID + (src.getTenantId() == null ? " IS NULL" : " = '" + src.getTenantId() + "'") + " AND " +
TABLE_SCHEM + (src.getSchemaName() == null ? " IS NULL " : " = '" + src.getSchemaName() + "'") + " AND " +
TABLE_NAME + " = '" + src.getTableName() + "' AND " +
COLUMN_NAME + (dst.getTenantId() == null ? " IS NULL" : " = '" + dst.getTenantId() + "'") + " AND " +
COLUMN_FAMILY + " = '" + (dst.getSchemaName() == null ? dst.getTableName() : dst.getSchemaName() + "." +
dst.getTableName()) + "'";
phoenixConnection.createStatement().execute(deleteQuery);
phoenixConnection.commit();
}
private byte getLinkType(PTable.LinkType linkType) {
byte type;
if (linkType == PTable.LinkType.PHYSICAL_TABLE) {
type = PHYSICAL_TABLE_LINK;
}
else if (linkType == PTable.LinkType.PARENT_TABLE) {
type = PARENT_TABLE_LINK;
} else if (linkType == PTable.LinkType.CHILD_TABLE) {
type = CHILD_TABLE_LINK;
}
else {
throw new AssertionError("Unknown Link Type");
}
return type;
}
private PTable.LinkType getLinkType(byte linkType) {
PTable.LinkType type;
if (linkType == PHYSICAL_TABLE_LINK) {
type = PTable.LinkType.PHYSICAL_TABLE;
}
else if (linkType == PARENT_TABLE_LINK) {
type = PTable.LinkType.PARENT_TABLE;
} else if (linkType == CHILD_TABLE_LINK) {
type = PTable.LinkType.CHILD_TABLE;
}
else {
throw new AssertionError("Unknown Link Type");
}
return type;
}
private void removeOrLogOrphanLinks(PhoenixConnection phoenixConnection) {
for (Link link : orphanLinkSet) {
try {
byte linkType = getLinkType(link.type);
if (outputPath != null) {
writer[linkType].write(link.src.getSerializedValue() + "-->" + link.dst.getSerializedValue());
writer[linkType].newLine();
}
else if (!clean){
System.out.println(link.src.getSerializedValue() + "-(" + link.type + ")->" + link.dst.getSerializedValue());
}
if (clean) {
removeLink(phoenixConnection, link.src, link.dst, link.type);
}
} catch (Exception e) {
// ignore
}
}
}
private void forcefullyDropView(PhoenixConnection phoenixConnection,
Key key) throws Exception {
String deleteRowsFromCatalog = "DELETE FROM " + SYSTEM_CATALOG_NAME +
" WHERE " + TENANT_ID + (key.getTenantId() == null ? " IS NULL" : " = '" + key.getTenantId() + "'") + " AND " +
TABLE_SCHEM + (key.getSchemaName() == null ? " IS NULL " : " = '" + key.getSchemaName() + "'") + " AND " +
TABLE_NAME + " = '" + key.getTableName() + "'";
String deleteRowsFromChildLink = "DELETE FROM " + SYSTEM_CHILD_LINK_NAME +
" WHERE " + COLUMN_NAME + (key.getTenantId() == null ? " IS NULL" : " = '" + key.getTenantId() + "'") + " AND " +
COLUMN_FAMILY + " = '" + (key.getSchemaName() == null ? key.getTableName() : key.getSchemaName() + "." + key.getTableName()) + "'";
try {
phoenixConnection.createStatement().execute(deleteRowsFromCatalog);
phoenixConnection.createStatement().execute(deleteRowsFromChildLink);
phoenixConnection.commit();
} catch (SQLException e) {
throw new IOException(e);
}
}
private void dropOrLogOrphanViews(PhoenixConnection phoenixConnection, Configuration configuration,
Key key) throws Exception {
if (outputPath != null) {
writer[VIEW].write(key.getSerializedValue());
writer[VIEW].newLine();
}
else if (!clean) {
System.out.println(key.getSerializedValue());
return;
}
if (!clean) {
return;
}
gracefullyDropView(phoenixConnection, configuration, key);
}
/**
* Go through all the views in the system catalog table and add them to orphanViewSet
* @param phoenixConnection
* @throws Exception
*/
private void populateOrphanViewSet(PhoenixConnection phoenixConnection)
throws Exception {
ResultSet viewRS = phoenixConnection.createStatement().executeQuery(viewQuery);
while (viewRS.next()) {
String tenantId = viewRS.getString(1);
String schemaName = viewRS.getString(2);
String tableName = viewRS.getString(3);
Key key = new Key(tenantId, schemaName, tableName);
View view = new View(key);
orphanViewSet.put(key, view);
}
}
/**
* Go through all the tables in the system catalog table and update baseSet
* @param phoenixConnection
* @throws Exception
*/
private void populateBaseSet(PhoenixConnection phoenixConnection)
throws Exception {
ResultSet baseTableRS = phoenixConnection.createStatement().executeQuery(candidateBaseTableQuery);
while (baseTableRS.next()) {
String tenantId = baseTableRS.getString(1);
String schemaName = baseTableRS.getString(2);
String tableName = baseTableRS.getString(3);
Key key = new Key(tenantId, schemaName, tableName);
Base base = new Base(key);
baseSet.put(key, base);
}
}
/**
* Go through all the physical links in the system catalog table and update the base table info of the
* view objects in orphanViewSet. If the base or view object does not exist for a given link, then add the link
* to orphanLinkSet
* @param phoenixConnection
* @throws Exception
*/
private void processPhysicalLinks(PhoenixConnection phoenixConnection)
throws Exception {
ResultSet physicalLinkRS = phoenixConnection.createStatement().executeQuery(physicalLinkQuery);
while (physicalLinkRS.next()) {
String tenantId = physicalLinkRS.getString(1);
String schemaName = physicalLinkRS.getString(2);
String tableName = physicalLinkRS.getString(3);
Key viewKey = new Key(tenantId, schemaName, tableName);
View view = orphanViewSet.get(viewKey);
String baseTenantId = physicalLinkRS.getString(4);
String baseFullTableName = physicalLinkRS.getString(5);
Key baseKey = new Key(baseTenantId, baseFullTableName);
Base base = baseSet.get(baseKey);
if (view == null || base == null) {
orphanLinkSet.add(new Link(viewKey, baseKey, PTable.LinkType.PHYSICAL_TABLE));
}
else {
view.setBase(baseKey);
}
}
}
/**
* Go through all the child-parent links and update the parent field of the view objects of orphanViewSet.
* Check if the child does not exist add the link to orphanLinkSet.
* @param phoenixConnection
* @throws Exception
*/
private void processChildParentLinks(PhoenixConnection phoenixConnection)
throws Exception {
ResultSet childParentLinkRS = phoenixConnection.createStatement().executeQuery(childParentLinkQuery);
while (childParentLinkRS.next()) {
String childTenantId = childParentLinkRS.getString(1);
String childSchemaName = childParentLinkRS.getString(2);
String childTableName = childParentLinkRS.getString(3);
Key childKey = new Key(childTenantId, childSchemaName, childTableName);
View childView = orphanViewSet.get(childKey);
String parentTenantId = childParentLinkRS.getString(4);
String parentFullTableName = childParentLinkRS.getString(5);
Key parentKey = new Key(parentTenantId, parentFullTableName);
View parentView = orphanViewSet.get(parentKey);
// Check if parentTenantId is not set but it should have been the same as the childTenantId. Is this a bug?
if (childView != null && parentView == null && parentTenantId == null && childTenantId != null) {
Key anotherParentKey = new Key(childTenantId, parentFullTableName);
parentView = orphanViewSet.get(anotherParentKey);
if (parentView != null) {
parentKey = anotherParentKey;
}
}
if (childView == null || parentView == null) {
orphanLinkSet.add(new Link(childKey, parentKey, PTable.LinkType.PARENT_TABLE));
}
else {
childView.setParent(parentKey);
}
}
}
/**
* Go through all the parent-child links and update the parent field of the
* child view objects of orphanViewSet and the child links of the parent objects (which can be a view from
* orphanViewSet or a base table from baseSet. Check if the child or parent does not exist, and if so, add the link
* to orphanLinkSet.
* @param phoenixConnection
* @throws Exception
*/
private void processParentChildLinks(PhoenixConnection phoenixConnection)
throws Exception {
ResultSet parentChildLinkRS = phoenixConnection.createStatement().executeQuery(parentChildLinkQuery);
while (parentChildLinkRS.next()) {
String tenantId = parentChildLinkRS.getString(1);
String schemaName = parentChildLinkRS.getString(2);
String tableName = parentChildLinkRS.getString(3);
Key parentKey = new Key(tenantId, schemaName, tableName);
Base base = baseSet.get(parentKey);
View parentView = orphanViewSet.get(parentKey);
String childTenantId = parentChildLinkRS.getString(4);
String childFullTableName = parentChildLinkRS.getString(5);
Key childKey = new Key(childTenantId, childFullTableName);
View childView = orphanViewSet.get(childKey);
if (childView == null) {
// No child for this link
orphanLinkSet.add(new Link(parentKey, childKey, PTable.LinkType.CHILD_TABLE));
}
else if (base != null) {
base.addChild(childKey);
}
else if (parentView != null) {
parentView.addChild(childKey);
}
else {
// No parent for this link
orphanLinkSet.add(new Link(parentKey, childKey, PTable.LinkType.CHILD_TABLE));
}
}
}
private void removeBaseTablesWithNoChildViewFromBaseSet() {
Iterator<Map.Entry<Key, Base>> iterator = baseSet.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Key, Base> entry = iterator.next();
if (entry.getValue().childViews == null || entry.getValue().childViews.isEmpty()) {
iterator.remove();
}
}
}
/**
* Starting from the child views of the base tables from baseSet, visit views level by level and identify
* missing or broken links, and thereby identify orphan vies
*/
private void visitViewsLevelByLevelAndIdentifyOrphanViews() {
if (baseSet.isEmpty())
return;
HashMap<Key, View> viewSet = new HashMap<>();
viewSetArray.add(0, viewSet);
// Remove the child views of the tables of baseSet from orphanViewSet and add them to viewSetArray[0]
// if these views have the correct physical link
for (Map.Entry<Key, Base> baseEntry : baseSet.entrySet()) {
for (Key child : baseEntry.getValue().childViews) {
View childView = orphanViewSet.get(child);
if (childView != null &&
childView.base != null && childView.base.equals(baseEntry.getKey())) {
orphanViewSet.remove(child);
viewSet.put(child, childView);
}
}
}
HashMap<Key, View> parentViewSet = viewSet;
// Remove the child views of viewSetArray[N] from orphanViewSet and add them to viewSetArray[N+1]
// if these view have the correct physical link and parent link
maxViewLevel = 1;
for (int i = 1; !parentViewSet.isEmpty(); i++) {
HashMap<Key, View> childViewSet = new HashMap<>();
viewSetArray.add(i, childViewSet);
for (Map.Entry<Key, View> viewEntry : parentViewSet.entrySet()) {
View parentView = viewEntry.getValue();
Key parentKey = viewEntry.getKey();
if (parentView.isParent()) {
for (Key child : parentView.childViews) {
View childView = orphanViewSet.get(child);
if (childView != null &&
childView.parent != null && childView.parent.equals(parentKey) &&
childView.base != null && childView.base.equals(parentView.base)) {
orphanViewSet.remove(child);
childViewSet.put(child, childView);
}
}
}
}
parentViewSet = childViewSet;
maxViewLevel += 1;
}
}
private void identifyOrphanViews(PhoenixConnection phoenixConnection)
throws Exception {
if (inputPath != null) {
readOrphanViews();
return;
}
// Go through the views and add them to orphanViewSet
populateOrphanViewSet(phoenixConnection);
// Go through the candidate base tables and add them to baseSet
populateBaseSet(phoenixConnection);
// Go through physical links and update the views of orphanLinkSet
processPhysicalLinks(phoenixConnection);
// Go through the parent-child links and update the views of orphanViewSet and the tables of baseSet
processParentChildLinks(phoenixConnection);
// Go through index-view links and update the views of orphanLinkSet
processChildParentLinks(phoenixConnection);
if (baseSet == null)
return;
// Remove the base tables with no child from baseSet
removeBaseTablesWithNoChildViewFromBaseSet();
// Starting from the child views of the base tables, visit views level by level and identify
// missing or broken links and thereby identify orphan vies
visitViewsLevelByLevelAndIdentifyOrphanViews();
}
private void createSnapshot(PhoenixConnection phoenixConnection, long scn)
throws Exception {
phoenixConnection.getQueryServices().getAdmin().snapshot("OrphanViewTool." + Long.toString(scn),
TableName.valueOf(SYSTEM_CATALOG_NAME));
phoenixConnection.getQueryServices().getAdmin().snapshot("OrphanViewTool." + Long.toString(scn+1),
TableName.valueOf(SYSTEM_CHILD_LINK_NAME));
}
private void readOrphanViews() throws Exception {
String aLine;
reader[VIEW] = new BufferedReader(new FileReader(inputPath + fileName[VIEW]));
while ((aLine = reader[VIEW].readLine()) != null) {
Key key = new Key(aLine);
orphanViewSet.put(key, new View(key));
}
}
private void readAndRemoveOrphanLinks(PhoenixConnection phoenixConnection) throws Exception{
String aLine;
for (byte i = VIEW+1; i < ORPHAN_TYPE_COUNT; i++) {
reader[i] = new BufferedReader(new FileReader(inputPath + fileName[i]));
while ((aLine = reader[i].readLine()) != null) {
String ends[] = aLine.split("-->");
removeLink(phoenixConnection, new Key(ends[0]), new Key(ends[1]), getLinkType(i));
}
}
}
private void closeConnectionAndFiles(Connection connection) throws IOException {
tryClosingConnection(connection);
for (byte i = VIEW; i < ORPHAN_TYPE_COUNT; i++) {
if (writer[i] != null) {
writer[i].close();
}
if (reader[i] != null) {
reader[i].close();
}
}
}
/**
* Try closing a connection if it is not null
* @param connection connection object
* @throws RuntimeException if closing the connection fails
*/
private void tryClosingConnection(Connection connection) {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException sqlE) {
LOGGER.error("Failed to close connection: ", sqlE);
throw new RuntimeException("Failed to close connection with exception: ", sqlE);
}
}
/**
* Examples for input arguments:
* -c : cleans orphan views
* -c -op /tmp/ : cleans orphan views and links, and logs their names to the files named Orphan*.txt in /tmp/
* -i : identifies orphan views and links, and prints their names on the console
* -i -op /tmp/ : identifies orphan views and links, and logs the name of their names to files named Orphan*.txt in /tmp/
* -c -ip /tmp/ : cleans the views listed in files at /tmp/
*/
@Override
public int run(String[] args) throws Exception {
Connection connection = null;
try {
final Configuration configuration = HBaseConfiguration.addHbaseResources(getConf());
try {
parseOptions(args);
} catch (IllegalStateException e) {
printHelpAndExit(e.getMessage(), getOptions());
}
if (outputPath != null) {
// Create files to log orphan views and links
for (int i = VIEW; i < ORPHAN_TYPE_COUNT; i++) {
File file = new File(outputPath + fileName[i]);
if (file.exists()) {
file.delete();
}
file.createNewFile();
writer[i] = new BufferedWriter(new FileWriter(file));
}
}
Properties props = new Properties();
long scn = EnvironmentEdgeManager.currentTimeMillis() - ageMs;
props.setProperty("CurrentSCN", Long.toString(scn));
connection = ConnectionUtil.getInputConnection(configuration, props);
PhoenixConnection phoenixConnection = connection.unwrap(PhoenixConnection.class);
identifyOrphanViews(phoenixConnection);
if (clean) {
// Close the connection with SCN
phoenixConnection.close();
connection = ConnectionUtil.getInputConnection(configuration);
phoenixConnection = connection.unwrap(PhoenixConnection.class);
// Take a snapshot of system tables to be modified
createSnapshot(phoenixConnection, scn);
}
for (Map.Entry<Key, View> entry : orphanViewSet.entrySet()) {
try {
dropOrLogOrphanViews(phoenixConnection, configuration, entry.getKey());
} catch (Exception e) {
// Ignore
}
};
if (clean) {
// Wait for the view drop tasks in the SYSTEM.TASK table to be processed
long timeInterval = configuration.getLong(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB,
QueryServicesOptions.DEFAULT_TASK_HANDLING_INTERVAL_MS);
Thread.sleep(maxViewLevel * timeInterval);
// Clean up any remaining orphan view records from system tables
for (Map.Entry<Key, View> entry : orphanViewSet.entrySet()) {
try {
forcefullyDropView(phoenixConnection, entry.getKey());
} catch (Exception e) {
// Ignore
}
};
}
if (inputPath == null) {
removeOrLogOrphanLinks(phoenixConnection);
}
else {
readAndRemoveOrphanLinks(phoenixConnection);
}
return 0;
} catch (Exception ex) {
LOGGER.error("Orphan View Tool : An exception occurred "
+ ExceptionUtils.getMessage(ex) + " at:\n" +
ExceptionUtils.getStackTrace(ex));
return -1;
} finally {
closeConnectionAndFiles(connection);
}
}
public static void main(final String[] args) throws Exception {
int result = ToolRunner.run(new OrphanViewTool(), args);
System.exit(result);
}
}