PHOENIX-4848 - Do not propagate unrequired metadata changes and handle missing/corrupted child views
This patch fixes two related issues on propagating metadata changes to views. The first issue is a bug in the logic to determine if a given change should be propagated. The second issue is in handling missing or corrupted views while attempting to propagate a change. If a view is missing or corrupted, this patch simply ignores the view by catching its loadTable() exception and logging it. Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/5879fa0f Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/5879fa0f Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/5879fa0f Branch: refs/heads/4.14-cdh5.11 Commit: 5879fa0f6e66be8d269faf43582399e6abcfbe3b Parents: e771aea Author: Kadir <kozde...@salesforce.com> Authored: Wed Aug 15 18:34:17 2018 +0100 Committer: Pedro Boado <pbo...@apache.org> Committed: Wed Oct 17 20:23:16 2018 +0100 ---------------------------------------------------------------------- .../phoenix/end2end/AlterTableWithViewsIT.java | 128 ++++++++++++++++++- .../coprocessor/MetaDataEndpointImpl.java | 19 ++- 2 files changed, 138 insertions(+), 9 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/phoenix/blob/5879fa0f/phoenix-core/src/it/java/org/apache/phoenix/end2end/AlterTableWithViewsIT.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/AlterTableWithViewsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/AlterTableWithViewsIT.java index ab3a4ab..c4e4995 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/AlterTableWithViewsIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/AlterTableWithViewsIT.java @@ -36,6 +36,7 @@ import java.util.Collection; import java.util.Properties; import org.apache.commons.lang.ArrayUtils; +import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.HTableInterface; import org.apache.hadoop.hbase.util.Bytes; import org.apache.phoenix.coprocessor.TephraTransactionalProcessor; @@ -49,6 +50,7 @@ import org.apache.phoenix.schema.PTable; import org.apache.phoenix.schema.PTableKey; import org.apache.phoenix.schema.PTableType; import org.apache.phoenix.util.PropertiesUtil; +import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.StringUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Test; @@ -100,7 +102,7 @@ public class AlterTableWithViewsIT extends ParallelStatsDisabledIT { @Test public void testAddNewColumnsToBaseTableWithViews() throws Exception { try (Connection conn = DriverManager.getConnection(getUrl()); - Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ) { + Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ) { String tableName = generateUniqueName(); String viewOfTable = tableName + "_VIEW"; String ddlFormat = "CREATE TABLE IF NOT EXISTS " + tableName + " (" @@ -111,17 +113,64 @@ public class AlterTableWithViewsIT extends ParallelStatsDisabledIT { + " ) %s"; conn.createStatement().execute(generateDDL(ddlFormat)); assertTableDefinition(conn, tableName, PTableType.TABLE, null, 0, 3, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2"); - + viewConn.createStatement().execute("CREATE VIEW " + viewOfTable + " ( VIEW_COL1 DECIMAL(10,2), VIEW_COL2 VARCHAR ) AS SELECT * FROM " + tableName); assertTableDefinition(conn, viewOfTable, PTableType.VIEW, tableName, 0, 5, 3, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2"); - + // adding a new pk column and a new regular column conn.createStatement().execute("ALTER TABLE " + tableName + " ADD COL3 varchar(10) PRIMARY KEY, COL4 integer"); assertTableDefinition(conn, tableName, PTableType.TABLE, null, columnEncoded ? 2 : 1, 5, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2", "COL3", "COL4"); assertTableDefinition(conn, viewOfTable, PTableType.VIEW, tableName, 1, 7, 5, "ID", "COL1", "COL2", "COL3", "COL4", "VIEW_COL1", "VIEW_COL2"); - } + } } - + + @Test + public void testAddNewColumnsToBaseTableWithVCorruptedViews() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl()); + Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ) { + String tableName = generateUniqueName(); + String viewOfTable = tableName + "_VIEW"; + String ddlFormat = "CREATE TABLE IF NOT EXISTS " + tableName + " (" + + " %s ID char(1) NOT NULL," + + " COL1 integer NOT NULL," + + " COL2 bigint NOT NULL," + + " CONSTRAINT NAME_PK PRIMARY KEY (%s ID, COL1, COL2)" + + " ) %s"; + conn.createStatement().execute(generateDDL(ddlFormat)); + assertTableDefinition(conn, tableName, PTableType.TABLE, null, 0, 3, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2"); + + viewConn.createStatement().execute("CREATE VIEW IF NOT EXISTS " + viewOfTable + " ( VIEW_COL1 DECIMAL(10,2), VIEW_COL2 VARCHAR ) AS SELECT * FROM " + tableName); + assertTableDefinition(conn, viewOfTable, PTableType.VIEW, tableName, 0, 5, 3, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2"); + PhoenixConnection phoenixConn = conn.unwrap(PhoenixConnection.class); + PName tenantId = isMultiTenant ? PNameFactory.newName("tenant1") : null; + + // corrupt the table view + byte[] rowKey = + SchemaUtil.getTableKey(tenantId == null ? new byte[0] : Bytes.toBytes(tenantId.toString()), + Bytes.toBytes(SchemaUtil.getSchemaNameFromFullName(viewOfTable)), + Bytes.toBytes(viewOfTable)); + try (HTableInterface htable = + phoenixConn.getQueryServices().getTable( + Bytes.toBytes(PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME))) { + Delete del = new Delete(rowKey); + del.deleteColumn(PhoenixDatabaseMetaData.TABLE_FAMILY_BYTES, PhoenixDatabaseMetaData.TABLE_TYPE_BYTES); + htable.delete(del); + } + // adding a new pk column and a new regular column; the corrupted view will be ignored as + // any Throwable exception thrown by loadTable() will be caught while loading views + conn.createStatement().execute("ALTER TABLE " + tableName + " ADD COL3 varchar(10) PRIMARY KEY, COL4 integer"); + assertTableDefinition(conn, tableName, PTableType.TABLE, null, columnEncoded ? 2 : 1, 5, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2", "COL3", "COL4"); + // since the view is corrupted, loading the view should result in an exception + try { + viewConn.createStatement().execute("SELECT * FROM "+viewOfTable); + fail("An exception was not raised! the corrupted view is loaded successfully"); + } + catch (Throwable t) { + // as expected a Throwable exception happened + } + } + } + @Test public void testAlterPropertiesOfParentTable() throws Exception { try (Connection conn = DriverManager.getConnection(getUrl()); @@ -193,7 +242,74 @@ public class AlterTableWithViewsIT extends ParallelStatsDisabledIT { assertTrue(rs.wasNull()); } } - + + @Test + public void testAlterPropertiesOfParentTableWithCorruptedViews() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl()); + Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ) { + String tableName = generateUniqueName(); + String viewOfTable = tableName + "_VIEW"; + String ddlFormat = "CREATE TABLE IF NOT EXISTS " + tableName + " (" + + " %s ID char(1) NOT NULL," + + " COL1 integer NOT NULL," + + " COL2 bigint NOT NULL," + + " CONSTRAINT NAME_PK PRIMARY KEY (%s ID, COL1, COL2)" + + " ) %s "; + conn.createStatement().execute(generateDDL("UPDATE_CACHE_FREQUENCY=2", ddlFormat)); + viewConn.createStatement().execute("CREATE VIEW IF NOT EXISTS " + viewOfTable + " ( VIEW_COL1 DECIMAL(10,2), VIEW_COL2 VARCHAR ) AS SELECT * FROM " + tableName); + + PhoenixConnection phoenixConn = conn.unwrap(PhoenixConnection.class); + PTable table = phoenixConn.getTable(new PTableKey(null, tableName)); + PName tenantId = isMultiTenant ? PNameFactory.newName("tenant1") : null; + + assertFalse(table.isImmutableRows()); + assertEquals(2, table.getUpdateCacheFrequency()); + PTable viewTable1 = viewConn.unwrap(PhoenixConnection.class).getTable(new PTableKey(tenantId, viewOfTable)); + assertFalse(viewTable1.isImmutableRows()); + assertEquals(2, viewTable1.getUpdateCacheFrequency()); + + // corrupt the table view + byte[] rowKey = + SchemaUtil.getTableKey(tenantId == null ? new byte[0] : Bytes.toBytes(tenantId.toString()), + Bytes.toBytes(SchemaUtil.getSchemaNameFromFullName(viewOfTable)), + Bytes.toBytes(viewOfTable)); + try (HTableInterface htable = + phoenixConn.getQueryServices().getTable( + Bytes.toBytes(PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME))) { + + Delete del = new Delete(rowKey); + del.deleteColumn(PhoenixDatabaseMetaData.TABLE_FAMILY_BYTES, PhoenixDatabaseMetaData.TABLE_TYPE_BYTES); + htable.delete(del); + } + // alter the properties of the base table; the corrupted view will be ignored as + // any Throwable exception thrown by loadTable() will be caught while loading views + conn.createStatement().execute("ALTER TABLE " + tableName + " SET IMMUTABLE_ROWS=true, UPDATE_CACHE_FREQUENCY=3"); + + phoenixConn = conn.unwrap(PhoenixConnection.class); + table = phoenixConn.getTable(new PTableKey(null, tableName)); + assertTrue(table.isImmutableRows()); + assertEquals(3, table.getUpdateCacheFrequency()); + + long gpw = 1000000; + conn.createStatement().execute("ALTER TABLE " + tableName + " SET GUIDE_POSTS_WIDTH=" + gpw); + + ResultSet rs; + DatabaseMetaData md = conn.getMetaData(); + rs = md.getTables("", "", StringUtil.escapeLike(tableName), null); + assertTrue(rs.next()); + assertEquals(gpw, rs.getLong(PhoenixDatabaseMetaData.GUIDE_POSTS_WIDTH)); + + // since the view is corrupted, loading the view should result in an exception + try { + viewConn.createStatement().execute("SELECT * FROM "+viewOfTable); + fail("An exception was not raised! the corrupted view is loaded successfully"); + } + catch (Throwable t) { + // as expected a Throwable exception happened + } + } + } + @Test public void testDropColumnsFromBaseTableWithView() throws Exception { try (Connection conn = DriverManager.getConnection(getUrl()); http://git-wip-us.apache.org/repos/asf/phoenix/blob/5879fa0f/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java b/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java index ae2fa66..db1f7c3 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java @@ -1827,7 +1827,14 @@ public class MetaDataEndpointImpl extends MetaDataProtocol implements Coprocesso byte[] viewTable = viewInfo.getViewName(); byte[] tableKey = SchemaUtil.getTableKey(viewtenantId, viewSchema, viewTable); ImmutableBytesPtr cacheKey = new ImmutableBytesPtr(tableKey); - PTable view = loadTable(env, tableKey, cacheKey, clientTimeStamp, clientTimeStamp, clientVersion); + PTable view = null; + try { + view = loadTable(env, tableKey, cacheKey, clientTimeStamp, clientTimeStamp, clientVersion); + } + catch (Throwable t) { + logger.error("Loading tenant view failed", t); + } + if (view == null) { logger.warn("Found orphan tenant view row in SYSTEM.CATALOG with tenantId:" + Bytes.toString(tenantId) + ", schema:" @@ -2557,7 +2564,13 @@ public class MetaDataEndpointImpl extends MetaDataProtocol implements Coprocesso // lock the rows corresponding to views so that no other thread can modify the view meta-data RowLock viewRowLock = acquireLock(region, viewKey, locks); - PTable view = doGetTable(viewKey, clientTimeStamp, viewRowLock, clientVersion); + PTable view = null; + try { + view = doGetTable(viewKey, clientTimeStamp, viewRowLock, clientVersion); + } + catch (Throwable t) { + logger.warn("Loading tenant view failed", t); + } if (view == null) { logger.warn("Found orphan tenant view row in SYSTEM.CATALOG with tenantId:" + Bytes.toString(tenantId) + ", schema:" @@ -3189,7 +3202,7 @@ public class MetaDataEndpointImpl extends MetaDataProtocol implements Coprocesso for (Mutation m : tableMetaData) { byte[] key = m.getRow(); int pkCount = getVarChars(key, rowKeyMetaData); - if (pkCount >= COLUMN_NAME_INDEX + if (pkCount > COLUMN_NAME_INDEX && Bytes.compareTo(schemaName, rowKeyMetaData[SCHEMA_NAME_INDEX]) == 0 && Bytes.compareTo(tableName, rowKeyMetaData[TABLE_NAME_INDEX]) == 0) { return true;