[
https://issues.apache.org/jira/browse/PHOENIX-6142?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17216988#comment-17216988
]
ASF GitHub Bot commented on PHOENIX-6142:
-----------------------------------------
ChinmaySKulkarni commented on a change in pull request #903:
URL: https://github.com/apache/phoenix/pull/903#discussion_r507951694
##########
File path:
phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewMetadataIT.java
##########
@@ -294,7 +321,246 @@ public void testRecreateDroppedTableWithChildViews()
throws Exception {
}
}
- private void runDropChildViewsTask() {
+ @Test
+ public void testAlterTableIsResilientToOrphanLinks() throws SQLException {
+ final String parent1TableName = generateUniqueName();
+ final String parent2TableName = generateUniqueName();
+ final String viewName = "V_" + generateUniqueName();
+ // Note that this column name is the same as the new column on the
child view
+ final String alterTableDDL = "ALTER TABLE %s ADD NEW_COL1 VARCHAR";
+ createOrphanLink(SCHEMA1, parent1TableName, parent2TableName, SCHEMA2,
viewName);
+
+ try (Connection conn = DriverManager.getConnection(getUrl());
+ Statement stmt = conn.createStatement()) {
+ // Should not fail since this table is unrelated to the view in
spite of
+ // the orphan parent->child link
+ stmt.execute(String.format(alterTableDDL,
+ SchemaUtil.getTableName(SCHEMA1, parent2TableName)));
+ try {
+ stmt.execute(String.format(alterTableDDL,
+ SchemaUtil.getTableName(SCHEMA1, parent1TableName)));
+ fail("Adding column should be disallowed since there is a
conflicting column type "
+ + "on the child view");
+ } catch (SQLException sqlEx) {
+ assertEquals(CANNOT_MUTATE_TABLE.getErrorCode(),
sqlEx.getErrorCode());
+ }
+ }
+ }
+
+ @Test
+ public void testDropTableIsResilientToOrphanLinks() throws SQLException {
+ final String parent1TableName = generateUniqueName();
+ final String parent2TableName = generateUniqueName();
+ final String viewName = "V_" + generateUniqueName();
+ final String dropTableNoCascadeDDL = "DROP TABLE %s ";
+ createOrphanLink(SCHEMA1, parent1TableName, parent2TableName, SCHEMA2,
viewName);
+
+ try (Connection conn = DriverManager.getConnection(getUrl());
+ Statement stmt = conn.createStatement()) {
+ // Should not fail since this table is unrelated to the view in
spite of
+ // the orphan parent->child link
+ stmt.execute(String.format(dropTableNoCascadeDDL,
+ SchemaUtil.getTableName(SCHEMA1, parent2TableName)));
+ try {
+ stmt.execute(String.format(dropTableNoCascadeDDL,
+ SchemaUtil.getTableName(SCHEMA1, parent1TableName)));
+ fail("Drop table without cascade should fail since there is a
child view");
+ } catch (SQLException sqlEx) {
+ assertEquals(CANNOT_MUTATE_TABLE.getErrorCode(),
sqlEx.getErrorCode());
+ }
+ }
+ }
+
+ /**
+ * Create a view hierarchy:
+ * parent1 parent2
+ * | |
+ * level1view1 level1view2
+ * | |
+ * t001.level2view1 t001.level2view2
+ * |
+ * t001.level3view1
+ *
+ * We induce orphan links by recreating the same view names on top of
different parents
+ */
+ @Test
+ public void testViewHierarchyWithOrphanLinks() throws Exception {
+ final List<TableInfo> expectedLegitChildViewsListForParent1 = new
ArrayList<>();
+ final List<TableInfo> expectedLegitChildViewsListForParent2 = new
ArrayList<>();
+ final String tenantId = "t001";
+ final String parent1TableName = "P1_" + generateUniqueName();
+ final String parent2TableName = "P2_" + generateUniqueName();
+ final String level1ViewName1 = "L1_V_1_" + generateUniqueName();
+ final String level1ViewName2 = "L1_V_2_" + generateUniqueName();
+ final String level2ViewName1 = "L2_V_1_" + generateUniqueName();
+ final String level2ViewName2 = "L2_V_2_" + generateUniqueName();
+ final String level3ViewName1 = "L3_V_1_" + generateUniqueName();
+ createOrphanLink(BASE_TABLE_SCHEMA, parent1TableName, parent2TableName,
+ CHILD_VIEW_LEVEL_1_SCHEMA, level1ViewName1);
+ expectedLegitChildViewsListForParent1.add(new TableInfo(
+ null, CHILD_VIEW_LEVEL_1_SCHEMA.getBytes(),
level1ViewName1.getBytes()));
+
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ // Create a legit view on top of parent2
+
conn.createStatement().execute(String.format(CREATE_CHILD_VIEW_LEVEL_1_DDL,
+ CHILD_VIEW_LEVEL_1_SCHEMA, level1ViewName2,
+ BASE_TABLE_SCHEMA, parent2TableName));
+ expectedLegitChildViewsListForParent2.add(new TableInfo(
+ null, CHILD_VIEW_LEVEL_1_SCHEMA.getBytes(),
level1ViewName2.getBytes()));
+ }
+ Properties props = new Properties();
+ props.put(TENANT_ID_ATTRIB, tenantId);
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+
conn.createStatement().execute(String.format(CREATE_CHILD_VIEW_LEVEL_2_DDL,
+ CHILD_VIEW_LEVEL_2_SCHEMA, level2ViewName1,
+ CHILD_VIEW_LEVEL_1_SCHEMA, level1ViewName1));
+ expectedLegitChildViewsListForParent1.add(new
TableInfo(tenantId.getBytes(),
+ CHILD_VIEW_LEVEL_2_SCHEMA.getBytes(),
level2ViewName1.getBytes()));
+
+
conn.createStatement().execute(String.format(CREATE_CHILD_VIEW_LEVEL_2_DDL,
+ CHILD_VIEW_LEVEL_2_SCHEMA, level2ViewName2,
+ CHILD_VIEW_LEVEL_1_SCHEMA, level1ViewName2));
+ expectedLegitChildViewsListForParent2.add(new
TableInfo(tenantId.getBytes(),
+ CHILD_VIEW_LEVEL_2_SCHEMA.getBytes(),
level2ViewName2.getBytes()));
+
+ // Try to recreate the same view on a different global view to
create an orphan link
+ try {
+
conn.createStatement().execute(String.format(CREATE_CHILD_VIEW_LEVEL_2_DDL,
+ CHILD_VIEW_LEVEL_2_SCHEMA, level2ViewName2,
+ CHILD_VIEW_LEVEL_1_SCHEMA, level1ViewName1));
+ fail("Creating the same view again should have failed");
+ } catch (TableAlreadyExistsException ignored) {
+ // expected
+ }
+ // Create a third level view
+
conn.createStatement().execute(String.format(CREATE_CHILD_VIEW_LEVEL_3_DDL,
+ CHILD_VIEW_LEVEL_3_SCHEMA, level3ViewName1,
+ CHILD_VIEW_LEVEL_2_SCHEMA, level2ViewName2));
+ expectedLegitChildViewsListForParent2.add(new
TableInfo(tenantId.getBytes(),
+ CHILD_VIEW_LEVEL_3_SCHEMA.getBytes(),
level3ViewName1.getBytes()));
+ }
+ /*
+ After this setup, SYSTEM.CHILD_LINK parent->child linking rows
will look like this:
+ parent1->level1view1
+ parent2->level1view1 (orphan)
+ parent2->level1view2
+
+ level1view1->t001.level2view1
+ level1view1->t001.level2view2 (orphan)
+ level1view2->t001.level2view2
+
+ t001.level2view2->t001.level3view1
+ */
+
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ ConnectionQueryServices cqs =
conn.unwrap(PhoenixConnection.class).getQueryServices();
+ try (Table childLinkTable =
cqs.getTable(SchemaUtil.getPhysicalName(
+ SYSTEM_LINK_HBASE_TABLE_NAME.toBytes(),
cqs.getProps()).getName())) {
+ Pair<List<PTable>, List<TableInfo>> allDescendants =
+ ViewUtil.findAllDescendantViews(childLinkTable,
cqs.getConfiguration(),
+ EMPTY_BYTE_ARRAY, BASE_TABLE_SCHEMA.getBytes(),
+ parent1TableName.getBytes(),
HConstants.LATEST_TIMESTAMP, false);
+ List<PTable> legitChildViews = allDescendants.getFirst();
+ List<TableInfo> orphanViews = allDescendants.getSecond();
+ // All of the orphan links are legit views of the other parent
so they don't count
+ // as orphan views for this parent
+ assertTrue(orphanViews.isEmpty());
+ assertLegitChildViews(expectedLegitChildViewsListForParent1,
legitChildViews);
+
+ allDescendants =
ViewUtil.findAllDescendantViews(childLinkTable,
+ cqs.getConfiguration(), EMPTY_BYTE_ARRAY,
BASE_TABLE_SCHEMA.getBytes(),
+ parent2TableName.getBytes(),
HConstants.LATEST_TIMESTAMP, false);
+ legitChildViews = allDescendants.getFirst();
+ orphanViews = allDescendants.getSecond();
+ // All of the orphan links are legit views of the other parent
so they don't count
+ // as orphan views for this parent
+ assertTrue(orphanViews.isEmpty());
+ assertLegitChildViews(expectedLegitChildViewsListForParent2,
legitChildViews);
+
+ // Drop the legitimate level 1 view that was on top of parent1
Review comment:
Sure, I can add more global views.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
> Make DDL operations resilient to orphan parent->child linking rows in
> SYSTEM.CHILD_LINK
> ---------------------------------------------------------------------------------------
>
> Key: PHOENIX-6142
> URL: https://issues.apache.org/jira/browse/PHOENIX-6142
> Project: Phoenix
> Issue Type: Bug
> Affects Versions: 5.0.0, 4.15.0
> Reporter: Chinmay Kulkarni
> Assignee: Chinmay Kulkarni
> Priority: Blocker
> Fix For: 5.1.0, 4.16.0
>
> Attachments: PHOENIX-6142-4.x-v1.patch, PHOENIX-6142.4.x.v2.patch,
> PHOENIX-6142.4.x.v3.patch, PHOENIX-6142.4.x.v4.patch
>
>
> We are targeting PHOENIX-6141 for 4.17. Until we have it, we should aim at
> making DDL operations resilient to orphan parent->child linking rows. DDL
> operations identified which can fail due to orphan rows are:
> # Any ALTER TABLE ADD/DROP/SET calls on the base table T will fail if there
> are orphan links from T to some already dropped view. This happens because
> the call to
> [MetaDataEndpointImpl.findAllChildViews()|https://github.com/apache/phoenix/blob/fece8e69b9c03c80db7a0801d99e5de31fe15ffa/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java#L2142]
> from
> [MetaDataEndpointImpl.mutateColumn()|https://github.com/apache/phoenix/blob/fece8e69b9c03c80db7a0801d99e5de31fe15ffa/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java#L2592]
> fails with a TableNotFoundException.
> # Any DROP TABLE/VIEW call without CASCADE will fail even though there are
> actually no child views since the orphan rows wrongly indicate that there are
> child views.
> # During the upgrade path for UpgradeUtil.syncUpdateCacheFreqAllIndexes(),
> we will just ignore any orphan views (for ex, see
> [this|https://github.com/apache/phoenix/blob/fece8e69b9c03c80db7a0801d99e5de31fe15ffa/phoenix-core/src/main/java/org/apache/phoenix/util/UpgradeUtil.java#L1368-L1374]),
> but the call to UpgradeUtil.upgradeTable() will fail with a
> TableNotFoundException for each orphan view.
> # During a CREATE TABLE/VIEW, we try to drop any views from the previous life
> of that table/view, however we might end up dropping a legitimate view (with
> the same name) which is on another table/view because of this.
> Before dropping any views that we see from a parent->child link, we need to
> ensure that the view is in fact a child view of the same table/view we think
> it is an orphan of.
--
This message was sent by Atlassian Jira
(v8.3.4#803005)