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:
[email protected]


Reply via email to