You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@phoenix.apache.org by GitBox <gi...@apache.org> on 2020/10/19 04:07:34 UTC

[GitHub] [phoenix] jpisaac commented on a change in pull request #903: PHOENIX-6142: Make DDL operations resilient to orphan parent->child linking rows in SYSTEM.CHILD_LINK

jpisaac commented on a change in pull request #903:
URL: https://github.com/apache/phoenix/pull/903#discussion_r507430114



##########
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:
       Wondering whether you want to add a test - 
   that validates that the order of dropping does not matter, in other words dropping level1view2 first.
   Also, wondering whether adding multiple global views(level1 views for a parent) would make for a more robust test suite?




----------------------------------------------------------------
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:
users@infra.apache.org