Repository: phoenix
Updated Branches:
  refs/heads/3.0 be6465a89 -> 3af2450d3


PHOENIX-1098 Support CASCADE option on DROP TABLE that drops all VIEWs (Jan 
Fernando)


Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo
Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/3af2450d
Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/3af2450d
Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/3af2450d

Branch: refs/heads/3.0
Commit: 3af2450d31ec53dbc84fcf3ad8208836acb1bba1
Parents: be6465a
Author: James Taylor <jtay...@salesforce.com>
Authored: Thu Sep 4 00:18:14 2014 -0700
Committer: James Taylor <jtay...@salesforce.com>
Committed: Thu Sep 4 00:18:14 2014 -0700

----------------------------------------------------------------------
 .../end2end/TenantSpecificTablesDDLIT.java      | 108 ++++++++++++++-
 .../java/org/apache/phoenix/end2end/ViewIT.java |  90 ++++++++++++-
 phoenix-core/src/main/antlr3/PhoenixSQL.g       |   5 +-
 .../coprocessor/MetaDataEndpointImpl.java       | 135 ++++++++++++++++---
 .../phoenix/coprocessor/MetaDataProtocol.java   |  11 +-
 .../apache/phoenix/jdbc/PhoenixStatement.java   |   8 +-
 .../phoenix/parse/DropTableStatement.java       |   9 +-
 .../apache/phoenix/parse/ParseNodeFactory.java  |   4 +-
 .../phoenix/query/ConnectionQueryServices.java  |   2 +-
 .../query/ConnectionQueryServicesImpl.java      |   4 +-
 .../query/ConnectionlessQueryServicesImpl.java  |   2 +-
 .../query/DelegateConnectionQueryServices.java  |   4 +-
 .../apache/phoenix/schema/MetaDataClient.java   |   8 +-
 13 files changed, 347 insertions(+), 43 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/3af2450d/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDDLIT.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDDLIT.java
 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDDLIT.java
index 79aa6c1..591efe1 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDDLIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDDLIT.java
@@ -331,7 +331,7 @@ public class TenantSpecificTablesDDLIT extends 
BaseTenantSpecificTablesIT {
     }
     
     @Test
-    public void testDropParentTableWithExistingTenantTable() throws Exception {
+    public void testDisallowDropParentTableWithExistingTenantTable() throws 
Exception {
         Properties props = new Properties();
         props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, 
Long.toString(nextTimestamp()));
         Connection conn = DriverManager.getConnection(getUrl(), props);
@@ -348,6 +348,112 @@ public class TenantSpecificTablesDDLIT extends 
BaseTenantSpecificTablesIT {
     }
     
     @Test
+    public void testAllowDropParentTableWithCascadeAndSingleTenantTable() 
throws Exception {
+           long ts = nextTimestamp();
+           Properties props = new Properties();
+           props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, 
Long.toString(ts));
+           Connection conn = DriverManager.getConnection(getUrl(), props);
+           Connection connTenant = null;
+    
+               try {
+                       // Drop Parent Table 
+                       conn.createStatement().executeUpdate("DROP TABLE " + 
PARENT_TABLE_NAME + " CASCADE");
+                       conn.close();
+                     
+                       props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, 
Long.toString(ts + 10));
+                       connTenant = 
DriverManager.getConnection(PHOENIX_JDBC_TENANT_SPECIFIC_URL, props);
+                       
+               validateTenantViewIsDropped(conn);              
+           } finally {
+               if (conn != null) {
+                       conn.close();
+               }
+               if (connTenant != null) {
+                       connTenant.close();
+               }
+           }
+    }
+    
+    
+    @Test
+    public void 
testAllDropParentTableWithCascadeWithMultipleTenantTablesAndIndexes() throws 
Exception {
+        // Create a second tenant table
+       createTestTable(PHOENIX_JDBC_TENANT_SPECIFIC_URL2, TENANT_TABLE_DDL, 
null, nextTimestamp());
+       //TODO Create some tenant specific table indexes
+        
+           long ts = nextTimestamp();
+           Properties props = new Properties();
+           props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, 
Long.toString(ts));
+           Connection conn = null;
+           Connection connTenant1 = null;
+           Connection connTenant2 = null;
+    
+               try {
+                       conn = DriverManager.getConnection(getUrl(), props);
+               DatabaseMetaData meta = conn.getMetaData();
+            ResultSet rs = meta.getSuperTables(null, null, 
StringUtil.escapeLike(TENANT_TABLE_NAME) + "%");
+            assertTrue(rs.next());
+            assertEquals(TENANT_ID2, 
rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
+            assertEquals(TENANT_TABLE_NAME, 
rs.getString(PhoenixDatabaseMetaData.TABLE_NAME));
+            assertEquals(PARENT_TABLE_NAME, 
rs.getString(PhoenixDatabaseMetaData.SUPERTABLE_NAME));
+            assertTrue(rs.next());
+            assertEquals(TENANT_ID, 
rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
+            assertEquals(TENANT_TABLE_NAME, 
rs.getString(PhoenixDatabaseMetaData.TABLE_NAME));
+            assertEquals(PARENT_TABLE_NAME, 
rs.getString(PhoenixDatabaseMetaData.SUPERTABLE_NAME));
+            assertTrue(rs.next());
+            assertEquals(TENANT_ID, 
rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
+            assertEquals(TENANT_TABLE_NAME_NO_TENANT_TYPE_ID, 
rs.getString(PhoenixDatabaseMetaData.TABLE_NAME));
+            assertEquals(PARENT_TABLE_NAME_NO_TENANT_TYPE_ID, 
rs.getString(PhoenixDatabaseMetaData.SUPERTABLE_NAME));
+            assertFalse(rs.next());
+            rs.close();
+            conn.close();
+            
+                       // Drop Parent Table 
+                       conn.createStatement().executeUpdate("DROP TABLE " + 
PARENT_TABLE_NAME + " CASCADE");
+                 
+                       // Validate Tenant Views are dropped
+                       props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, 
Long.toString(ts + 10));
+                       connTenant1 = 
DriverManager.getConnection(PHOENIX_JDBC_TENANT_SPECIFIC_URL, props);
+               validateTenantViewIsDropped(connTenant1);
+                       connTenant2 = 
DriverManager.getConnection(PHOENIX_JDBC_TENANT_SPECIFIC_URL2, props);
+               validateTenantViewIsDropped(connTenant2);
+               
+               // Validate Tenant Metadata is gone for the Tenant Table 
TENANT_TABLE_NAME
+                       conn = DriverManager.getConnection(getUrl(), props);
+               meta = conn.getMetaData();
+            rs = meta.getSuperTables(null, null, 
StringUtil.escapeLike(TENANT_TABLE_NAME) + "%");
+            assertTrue(rs.next());
+            assertEquals(TENANT_ID, 
rs.getString(PhoenixDatabaseMetaData.TABLE_CAT));
+            assertEquals(TENANT_TABLE_NAME_NO_TENANT_TYPE_ID, 
rs.getString(PhoenixDatabaseMetaData.TABLE_NAME));
+            assertEquals(PARENT_TABLE_NAME_NO_TENANT_TYPE_ID, 
rs.getString(PhoenixDatabaseMetaData.SUPERTABLE_NAME));
+            assertFalse(rs.next());
+            rs.close();
+               
+           } finally {
+               if (conn != null) {
+                       conn.close();
+               }
+               if (connTenant1 != null) {
+                       connTenant1.close();
+               }
+               if (connTenant2 != null) {
+                       connTenant2.close();
+               }
+           }
+    }
+
+       private void validateTenantViewIsDropped(Connection connTenant) throws 
SQLException {
+               // Try and drop tenant view, should throw TableNotFoundException
+               try {
+                       String ddl = "DROP VIEW " + TENANT_TABLE_NAME;
+                   connTenant.createStatement().execute(ddl);
+                   fail("Tenant specific view " + TENANT_TABLE_NAME + " should 
have been dropped when parent was dropped");
+               } catch (TableNotFoundException e) {
+                       //Expected
+               }
+       }
+    
+    @Test
     public void testTableMetadataScan() throws Exception {
         // create a tenant table with same name for a different tenant to make 
sure we are not picking it up in metadata scans for TENANT_ID
         String tenantId2 = "tenant2";

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3af2450d/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
index 394fa04..8ef1024 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
@@ -259,15 +259,99 @@ public class ViewIT extends BaseViewIT {
         } catch (TableNotFoundException ignore) {
         }
         ddl = "DROP TABLE s1.t";
+        validateCannotDropTableWithChildViewsWithoutCascade(conn, "s1.t");
+        ddl = "DROP VIEW v2";
+        conn.createStatement().execute(ddl);
+        ddl = "DROP TABLE s1.t";
+        conn.createStatement().execute(ddl);
+    }
+
+    
+    @Test
+    public void testDisallowDropOfColumnOnParentTable() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+        String ddl = "CREATE TABLE tp (k1 INTEGER NOT NULL, k2 INTEGER NOT 
NULL, v1 DECIMAL, CONSTRAINT pk PRIMARY KEY (k1, k2))";
+        conn.createStatement().execute(ddl);
+        ddl = "CREATE VIEW v1(v2 VARCHAR, v3 VARCHAR) AS SELECT * FROM tp 
WHERE v1 = 1.0";
+        conn.createStatement().execute(ddl);
+        
         try {
-            conn.createStatement().execute(ddl);
+            conn.createStatement().execute("ALTER TABLE tp DROP COLUMN v1");
             fail();
         } catch (SQLException e) {
             assertEquals(SQLExceptionCode.CANNOT_MUTATE_TABLE.getErrorCode(), 
e.getErrorCode());
         }
-        ddl = "DROP VIEW v2";
+    }
+   
+    @Test
+    public void testViewAndTableAndDropCascade() throws Exception {
+       // Setup
+        Connection conn = DriverManager.getConnection(getUrl());
+        String ddl = "CREATE TABLE s2.t (k INTEGER NOT NULL PRIMARY KEY, v1 
DATE)";
         conn.createStatement().execute(ddl);
-        ddl = "DROP TABLE s1.t";
+        ddl = "CREATE VIEW s2.v1 (v2 VARCHAR) AS SELECT * FROM s2.t WHERE k > 
5";
+        conn.createStatement().execute(ddl);
+        ddl = "CREATE VIEW s2.v2 (v2 VARCHAR) AS SELECT * FROM s2.t WHERE k > 
10";
+        conn.createStatement().execute(ddl);
+
+        validateCannotDropTableWithChildViewsWithoutCascade(conn, "s2.t");
+        
+        // Execute DROP...CASCADE
+        conn.createStatement().execute("DROP TABLE s2.t CASCADE");
+        
+        validateViewDoesNotExist(conn, "s2.v1");
+        validateViewDoesNotExist(conn, "s2.v2");
+    }
+    
+    @Test
+    public void testViewAndTableAndDropCascadeWithIndexes() throws Exception {
+        
+       // Setup - Tables and Views with Indexes
+       Connection conn = DriverManager.getConnection(getUrl());
+       
+        String ddl = "CREATE TABLE s3.t (k INTEGER NOT NULL PRIMARY KEY, v1 
DATE) IMMUTABLE_ROWS=true";
+        conn.createStatement().execute(ddl);
+        ddl = "CREATE INDEX IDX1 ON s3.t (v1)";
+        conn.createStatement().execute(ddl);
+        ddl = "CREATE VIEW s3.v1 (v2 VARCHAR) AS SELECT * FROM s3.t WHERE k > 
5";
+        conn.createStatement().execute(ddl);
+        ddl = "CREATE INDEX IDX2 ON s3.v1 (v2)";
+        conn.createStatement().execute(ddl);
+        ddl = "CREATE VIEW s3.v2 (v2 VARCHAR) AS SELECT * FROM s3.t WHERE k > 
10";
+        conn.createStatement().execute(ddl);
+        ddl = "CREATE INDEX IDX3 ON s3.v2 (v2)";
         conn.createStatement().execute(ddl);
+
+        validateCannotDropTableWithChildViewsWithoutCascade(conn, "s3.t");
+        
+        // Execute DROP...CASCADE
+        conn.createStatement().execute("DROP TABLE s3.t CASCADE");
+        
+        // Validate Views were deleted - Try and delete child views, should 
throw TableNotFoundException
+        validateViewDoesNotExist(conn, "s3.v1");
+        validateViewDoesNotExist(conn, "s3.v2");
     }
+
+
+       private void 
validateCannotDropTableWithChildViewsWithoutCascade(Connection conn, String 
tableName) throws SQLException {
+               String ddl;
+               try {
+               ddl = "DROP TABLE " + tableName;
+               conn.createStatement().execute(ddl);
+               fail("Should not be able to drop table " + tableName + " with 
child views without explictly specifying CASCADE");
+        }  catch (SQLException e) {
+            assertEquals(SQLExceptionCode.CANNOT_MUTATE_TABLE.getErrorCode(), 
e.getErrorCode());
+        }
+       }
+
+
+       private void validateViewDoesNotExist(Connection conn, String viewName) 
throws SQLException {
+               try {
+               String ddl1 = "DROP VIEW " + viewName;
+            conn.createStatement().execute(ddl1);
+            fail("View s3.v1 should have been deleted when parent was 
dropped");
+        } catch (TableNotFoundException e) {
+               //Expected
+        }
+       }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3af2450d/phoenix-core/src/main/antlr3/PhoenixSQL.g
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/antlr3/PhoenixSQL.g 
b/phoenix-core/src/main/antlr3/PhoenixSQL.g
index d1b69b8..6cc8d57 100644
--- a/phoenix-core/src/main/antlr3/PhoenixSQL.g
+++ b/phoenix-core/src/main/antlr3/PhoenixSQL.g
@@ -104,6 +104,7 @@ tokens
     MINVALUE='minvalue';
     MAXVALUE='maxvalue';
     CYCLE='cycle';
+    CASCADE='cascade';
 }
 
 
@@ -469,8 +470,8 @@ column_names returns [List<ColumnName> ret]
        
 // Parse a drop table statement.
 drop_table_node returns [DropTableStatement ret]
-    :   DROP (v=VIEW | TABLE) (IF ex=EXISTS)? t=from_table_name
-        {ret = factory.dropTable(t, v==null ? PTableType.TABLE : 
PTableType.VIEW, ex!=null); }
+    :   DROP (v=VIEW | TABLE) (IF ex=EXISTS)? t=from_table_name (c=CASCADE)?
+        {ret = factory.dropTable(t, v==null ? PTableType.TABLE : 
PTableType.VIEW, ex!=null, c!=null); }
     ;
 
 // Parse a drop index statement

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3af2450d/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 b7343d1..9af606e 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
@@ -77,10 +77,12 @@ import org.apache.hadoop.hbase.filter.Filter;
 import org.apache.hadoop.hbase.filter.FilterList;
 import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
 import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.hbase.regionserver.HRegion;
 import org.apache.hadoop.hbase.regionserver.RegionScanner;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
+import org.apache.hadoop.io.WritableUtils;
 import org.apache.phoenix.cache.GlobalCache;
 import org.apache.phoenix.hbase.index.util.GenericKeyValueBuilder;
 import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
@@ -104,6 +106,7 @@ import org.apache.phoenix.schema.PTableImpl;
 import org.apache.phoenix.schema.PTableType;
 import org.apache.phoenix.schema.SortOrder;
 import org.apache.phoenix.schema.TableNotFoundException;
+import org.apache.phoenix.schema.tuple.ResultTuple;
 import org.apache.phoenix.util.ByteUtil;
 import org.apache.phoenix.util.IndexUtil;
 import org.apache.phoenix.util.KeyValueUtil;
@@ -597,10 +600,10 @@ public class MetaDataEndpointImpl extends 
BaseEndpointCoprocessor implements Met
     protected static final byte[] PHYSICAL_TABLE_BYTES = new byte[] 
{PTable.LinkType.PHYSICAL_TABLE.getSerializedValue()};
     /**
      * @param tableName parent table's name
-     * @return true if there exist a table that use this table as their base 
table.
+     * Looks for whether child views exist for the table specified by table.
      * TODO: should we pass a timestamp here?
      */
-    private boolean hasViews(HRegion region, byte[] tenantId, PTable table) 
throws IOException {
+    private TableViewFinderResult findChildViews(HRegion region, byte[] 
tenantId, PTable table) throws IOException {
         byte[] schemaName = table.getSchemaName().getBytes();
         byte[] tableName = table.getTableName().getBytes();
         boolean isMultiTenant = table.isMultiTenant();
@@ -622,22 +625,42 @@ public class MetaDataEndpointImpl extends 
BaseEndpointCoprocessor implements Met
         scan.setFilter(filter);
         scan.addColumn(TABLE_FAMILY_BYTES, LINK_TYPE_BYTES);
         HTableInterface hTable = 
getEnvironment().getTable(PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME_BYTES);
+        ResultScanner scanner = hTable.getScanner(scan);
+
+        boolean allViewsInCurrentRegion = true;
+        int numOfChildViews = 0;
+        List<Result> results = Lists.newArrayList();
         try {
-            ResultScanner scanner = hTable.getScanner(scan);
-            try {
-                Result result = scanner.next();
-                return result != null;
-            }
-            finally {
-                scanner.close();
+            for (Result result = scanner.next(); (result != null); result = 
scanner.next()) {
+                numOfChildViews++;
+                ImmutableBytesWritable ptr = new ImmutableBytesWritable();
+                ResultTuple resultTuple = new ResultTuple(result);
+                resultTuple.getKey(ptr);
+                byte[] key = ptr.copyBytes();
+                if (checkTableKeyInRegion(key, region) != null) {
+                    allViewsInCurrentRegion = false;
+                }
+                results.add(result);
             }
         } finally {
+            scanner.close();
             hTable.close();
         }
+        TableViewFinderResult tableViewFinderResult = new 
TableViewFinderResult(results);
+        if (numOfChildViews > 0 && !allViewsInCurrentRegion) {
+            tableViewFinderResult.setAllViewsNotInSingleRegion();
+        }
+        return tableViewFinderResult;
+
     }
-    
+
     @Override
     public MetaDataMutationResult dropTable(List<Mutation> tableMetadata, 
String tableType) throws IOException {
+        return dropTable(tableMetadata, tableType, false);
+    }
+    
+    @Override
+    public MetaDataMutationResult dropTable(List<Mutation> tableMetadata, 
String tableType, boolean isCascade) throws IOException {
         byte[][] rowKeyMetaData = new byte[3][];
         
MetaDataUtil.getTenantIdAndSchemaAndTableName(tableMetadata,rowKeyMetaData);
         byte[] tenantIdBytes = 
rowKeyMetaData[PhoenixDatabaseMetaData.TENANT_ID_INDEX];
@@ -667,7 +690,7 @@ public class MetaDataEndpointImpl extends 
BaseEndpointCoprocessor implements Met
                     acquireLock(region, key, lids);
                 }
                 List<ImmutableBytesPtr> invalidateList = new 
ArrayList<ImmutableBytesPtr>();
-                result = doDropTable(key, tenantIdBytes, schemaName, 
tableName, parentTableName, PTableType.fromSerializedValue(tableType), 
tableMetadata, invalidateList, lids, tableNamesToDelete);
+                result = doDropTable(key, tenantIdBytes, schemaName, 
tableName, parentTableName, PTableType.fromSerializedValue(tableType), 
tableMetadata, invalidateList, lids, tableNamesToDelete, isCascade);
                 if (result.getMutationCode() != 
MutationCode.TABLE_ALREADY_EXISTS) {
                     return result;
                 }
@@ -693,7 +716,8 @@ public class MetaDataEndpointImpl extends 
BaseEndpointCoprocessor implements Met
     }
 
     private MetaDataMutationResult doDropTable(byte[] key, byte[] tenantId, 
byte[] schemaName, byte[] tableName, byte[] parentTableName, 
-            PTableType tableType, List<Mutation> rowsToDelete, 
List<ImmutableBytesPtr> invalidateList, List<Integer> lids, List<byte[]> 
tableNamesToDelete) throws IOException, SQLException {
+            PTableType tableType, List<Mutation> rowsToDelete, 
List<ImmutableBytesPtr> invalidateList, List<Integer> lids, List<byte[]> 
tableNamesToDelete,
+            boolean isCascade) throws IOException, SQLException {
         long clientTimeStamp = MetaDataUtil.getClientTimeStamp(rowsToDelete);
 
         RegionCoprocessorEnvironment env = getEnvironment();
@@ -702,7 +726,7 @@ public class MetaDataEndpointImpl extends 
BaseEndpointCoprocessor implements Met
         
         Cache<ImmutableBytesPtr,PTable> metaDataCache = 
GlobalCache.getInstance(this.getEnvironment()).getMetaDataCache();
         PTable table = metaDataCache.getIfPresent(cacheKey);
-        
+       
         // We always cache the latest version - fault in if not in cache
         if (table != null || (table = buildTable(key, cacheKey, region, 
HConstants.LATEST_TIMESTAMP)) != null) {
             if (table.getTimeStamp() < clientTimeStamp) {
@@ -736,9 +760,41 @@ public class MetaDataEndpointImpl extends 
BaseEndpointCoprocessor implements Met
         if (results.isEmpty()) { // Should not be possible
             return new MetaDataMutationResult(MutationCode.TABLE_NOT_FOUND, 
EnvironmentEdgeManager.currentTimeMillis(), null);
         }
-        if (hasViews(region, tenantId, table)) {
-            return new 
MetaDataMutationResult(MutationCode.UNALLOWED_TABLE_MUTATION, 
EnvironmentEdgeManager.currentTimeMillis(), null);
-        }
+
+        // Handle any child views that exist
+        TableViewFinderResult tableViewFinderResult = findChildViews(region, 
tenantId, table);
+        if (tableViewFinderResult.hasViews()) {
+               if (isCascade) {
+                       if (tableViewFinderResult.allViewsInMultipleRegions()) {
+                           // We don't yet support deleting a table with views 
where SYSTEM.CATALOG has split and the 
+                               // view metadata spans multiple regions
+                               return new 
MetaDataMutationResult(MutationCode.UNALLOWED_TABLE_MUTATION, 
EnvironmentEdgeManager.currentTimeMillis(), null);
+                       } else if 
(tableViewFinderResult.allViewsInSingleRegion()) {
+                               // Recursively delete views - safe as all the 
views as all in the same region
+                               for (Result viewResult : 
tableViewFinderResult.getResults()) {
+                               byte[][] rowKeyMetaData = new byte[3][];
+                               getVarChars(viewResult.getRow(), 3, 
rowKeyMetaData);
+                               byte[] viewTenantId = 
rowKeyMetaData[PhoenixDatabaseMetaData.TENANT_ID_INDEX];
+                               byte[] viewSchemaName = 
rowKeyMetaData[PhoenixDatabaseMetaData.SCHEMA_NAME_INDEX];
+                               byte[] viewName = 
rowKeyMetaData[PhoenixDatabaseMetaData.TABLE_NAME_INDEX];
+                               byte[] viewKey = 
SchemaUtil.getTableKey(viewTenantId, viewSchemaName, viewName);
+                               Delete delete = new Delete(viewKey, 
clientTimeStamp);
+                               rowsToDelete.add(delete);
+                               acquireLock(region, viewKey, lids);
+                               MetaDataMutationResult result =
+                                       doDropTable(viewKey, viewTenantId, 
viewSchemaName, viewName, null, PTableType.VIEW,
+                                           rowsToDelete, invalidateList, lids, 
tableNamesToDelete, false);
+                               if (result.getMutationCode() != 
MutationCode.TABLE_ALREADY_EXISTS) {
+                                   return result;
+                               }
+                                       }
+                       }       
+               } else {
+               // DROP without CASCADE on tables with child views is not 
permitted
+               return new 
MetaDataMutationResult(MutationCode.UNALLOWED_TABLE_MUTATION, 
EnvironmentEdgeManager.currentTimeMillis(), null);
+            }
+        } 
+        
         if (tableType != PTableType.VIEW) { // Add to list of HTables to 
delete, unless it's a view
             tableNamesToDelete.add(table.getName().getBytes());
         }
@@ -775,7 +831,7 @@ public class MetaDataEndpointImpl extends 
BaseEndpointCoprocessor implements Met
             Delete delete = new Delete(indexKey, clientTimeStamp, null);
             rowsToDelete.add(delete);
             acquireLock(region, indexKey, lids);
-            MetaDataMutationResult result = doDropTable(indexKey, tenantId, 
schemaName, indexName, tableName, PTableType.INDEX, rowsToDelete, 
invalidateList, lids, tableNamesToDelete);
+            MetaDataMutationResult result = doDropTable(indexKey, tenantId, 
schemaName, indexName, tableName, PTableType.INDEX, rowsToDelete, 
invalidateList, lids, tableNamesToDelete, false);
             if (result.getMutationCode() != MutationCode.TABLE_ALREADY_EXISTS) 
{
                 return result;
             }
@@ -854,7 +910,7 @@ public class MetaDataEndpointImpl extends 
BaseEndpointCoprocessor implements Met
                     if (type != expectedType) {
                         return new 
MetaDataMutationResult(MutationCode.TABLE_NOT_FOUND, 
EnvironmentEdgeManager.currentTimeMillis(), null);
                     }
-                    if (hasViews(region, tenantId, table)) {
+                    if (findChildViews(region, tenantId, table).hasViews()) {
                         // Disallow any column mutations for parents of tenant 
tables
                         return new 
MetaDataMutationResult(MutationCode.UNALLOWED_TABLE_MUTATION, 
EnvironmentEdgeManager.currentTimeMillis(), null);
                     }
@@ -977,7 +1033,7 @@ public class MetaDataEndpointImpl extends 
BaseEndpointCoprocessor implements Met
                                             byte[] linkKey = 
MetaDataUtil.getParentLinkKey(tenantId, schemaName, tableName, 
index.getTableName().getBytes());
                                             // Drop the link between the data 
table and the index table
                                             additionalTableMetaData.add(new 
Delete(linkKey, clientTimeStamp, null));
-                                            doDropTable(indexKey, tenantId, 
index.getSchemaName().getBytes(), index.getTableName().getBytes(), tableName, 
index.getType(), additionalTableMetaData, invalidateList, lids, 
tableNamesToDelete);
+                                            doDropTable(indexKey, tenantId, 
index.getSchemaName().getBytes(), index.getTableName().getBytes(), tableName, 
index.getType(), additionalTableMetaData, invalidateList, lids, 
tableNamesToDelete, false);
                                             // TODO: return in result?
                                         } else {
                                             invalidateList.add(new 
ImmutableBytesPtr(indexKey));
@@ -1247,4 +1303,45 @@ public class MetaDataEndpointImpl extends 
BaseEndpointCoprocessor implements Met
         return new MetaDataMutationResult(MutationCode.TABLE_NOT_IN_REGION,
                 EnvironmentEdgeManager.currentTimeMillis(), null);
     }
+    
+    /**
+     * Certain operations, such as DROP TABLE are not allowed if there a table 
has child views.
+     * This class wraps the Results of a scanning the Phoenix Metadata for 
child views for a specific table
+     * and stores an additional flag for whether whether SYSTEM.CATALOG has 
split across multiple regions.
+     */
+    private static class TableViewFinderResult {
+        
+        private List<Result> results = Lists.newArrayList();
+        private boolean allViewsNotInSingleRegion = false;
+        
+        private TableViewFinderResult(List<Result> results) {
+            this.results = results;
+        }
+        
+        public boolean hasViews() {
+            return results.size() > 0;
+        }
+
+        private void setAllViewsNotInSingleRegion() {
+            allViewsNotInSingleRegion = true;
+        }
+        
+        private List<Result> getResults() {
+            return results;
+        }
+        
+        /**
+         * Returns true is the table has views and they are all in the same 
HBase region.
+         */
+        private boolean allViewsInSingleRegion() {
+            return results.size() > 0 && !allViewsNotInSingleRegion;
+        }
+        
+        /**
+         * Returns true is the table has views and they are all NOT in the 
same HBase region.
+         */
+        private boolean allViewsInMultipleRegions() {
+            return results.size() > 0 && allViewsNotInSingleRegion;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3af2450d/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataProtocol.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataProtocol.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataProtocol.java
index a303b95..351662b 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataProtocol.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataProtocol.java
@@ -226,13 +226,22 @@ public interface MetaDataProtocol extends 
CoprocessorProtocol {
     MetaDataMutationResult createTable(List<Mutation> tableMetadata) throws 
IOException;
 
     /**
-     * Drop an existing Phoenix table
+     * Drop an existing Phoenix table. This is for backwards compatibility 
after adding option to CASCADE.
      * @param tableMetadata
      * @param tableType
      * @return MetaDataMutationResult
      * @throws IOException
      */
     MetaDataMutationResult dropTable(List<Mutation> tableMetadata, String 
tableType) throws IOException;
+    
+    /**
+     * Drop an existing Phoenix table
+     * @param tableMetadata
+     * @param tableType
+     * @return MetaDataMutationResult
+     * @throws IOException
+     */
+    MetaDataMutationResult dropTable(List<Mutation> tableMetadata, String 
tableType, boolean isCascade) throws IOException;
 
     /**
      * Add a column to an existing Phoenix table

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3af2450d/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java 
b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
index 3265206..45e6973 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
@@ -524,8 +524,8 @@ public class PhoenixStatement implements Statement, 
SQLCloseable, org.apache.pho
 
     private static class ExecutableDropTableStatement extends 
DropTableStatement implements CompilableStatement {
 
-        ExecutableDropTableStatement(TableName tableName, PTableType 
tableType, boolean ifExists) {
-            super(tableName, tableType, ifExists);
+        ExecutableDropTableStatement(TableName tableName, PTableType 
tableType, boolean ifExists, boolean cascade) {
+            super(tableName, tableType, ifExists, cascade);
         }
 
         @SuppressWarnings("unchecked")
@@ -781,8 +781,8 @@ public class PhoenixStatement implements Statement, 
SQLCloseable, org.apache.pho
         }
         
         @Override
-        public DropTableStatement dropTable(TableName tableName, PTableType 
tableType, boolean ifExists) {
-            return new ExecutableDropTableStatement(tableName, tableType, 
ifExists);
+        public DropTableStatement dropTable(TableName tableName, PTableType 
tableType, boolean ifExists, boolean cascade) {
+            return new ExecutableDropTableStatement(tableName, tableType, 
ifExists, cascade);
         }
         
         @Override

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3af2450d/phoenix-core/src/main/java/org/apache/phoenix/parse/DropTableStatement.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/parse/DropTableStatement.java 
b/phoenix-core/src/main/java/org/apache/phoenix/parse/DropTableStatement.java
index 2945d36..997b695 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/parse/DropTableStatement.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/parse/DropTableStatement.java
@@ -24,11 +24,14 @@ public class DropTableStatement extends MutableStatement {
     private final TableName tableName;
     private final boolean ifExists;
     private final PTableType tableType;
+    private final boolean cascade;
+    
 
-    protected DropTableStatement(TableName tableName, PTableType tableType, 
boolean ifExists) {
+    protected DropTableStatement(TableName tableName, PTableType tableType, 
boolean ifExists, boolean cascade) {
         this.tableName = tableName;
         this.tableType = tableType;
         this.ifExists = ifExists;
+        this.cascade = cascade;
     }
     
     @Override
@@ -48,6 +51,10 @@ public class DropTableStatement extends MutableStatement {
         return ifExists;
     }
     
+    public boolean cascade() {
+       return cascade;
+    }
+    
     @Override
     public Operation getOperation() {
         return Operation.DELETE;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3af2450d/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java 
b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
index 4fd5ab8..f1e39eb 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
@@ -299,8 +299,8 @@ public class ParseNodeFactory {
         return new DropColumnStatement(table, tableType, columnNodes, 
ifExists);
     }
     
-    public DropTableStatement dropTable(TableName tableName, PTableType 
tableType, boolean ifExists) {
-        return new DropTableStatement(tableName, tableType, ifExists);
+    public DropTableStatement dropTable(TableName tableName, PTableType 
tableType, boolean ifExists, boolean cascade) {
+        return new DropTableStatement(tableName, tableType, ifExists, cascade);
     }
     
     public DropIndexStatement dropIndex(NamedNode indexName, TableName 
tableName, boolean ifExists) {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3af2450d/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServices.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServices.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServices.java
index fd44d6b..5f43a63 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServices.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServices.java
@@ -71,7 +71,7 @@ public interface ConnectionQueryServices extends 
QueryServices, MetaDataMutated
 
     public MetaDataMutationResult getTable(PName tenantId, byte[] schemaName, 
byte[] tableName, long tableTimestamp, long clientTimetamp) throws SQLException;
     public MetaDataMutationResult createTable(List<Mutation> tableMetaData, 
byte[] tableName, PTableType tableType, Map<String,Object> tableProps, 
List<Pair<byte[],Map<String,Object>>> families, byte[][] splits) throws 
SQLException;
-    public MetaDataMutationResult dropTable(List<Mutation> tableMetadata, 
PTableType tableType) throws SQLException;
+    public MetaDataMutationResult dropTable(List<Mutation> tableMetadata, 
PTableType tableType, boolean cascade) throws SQLException;
     public MetaDataMutationResult addColumn(List<Mutation> tableMetaData, 
List<Pair<byte[],Map<String,Object>>> families, PTable table) throws 
SQLException;
     public MetaDataMutationResult dropColumn(List<Mutation> tableMetadata, 
PTableType tableType) throws SQLException;
     public MetaDataMutationResult updateIndexState(List<Mutation> 
tableMetadata, String parentTableName) throws SQLException;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3af2450d/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
index f811c6e..3633724 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
@@ -1026,7 +1026,7 @@ public class ConnectionQueryServicesImpl extends 
DelegateQueryServices implement
     }
 
     @Override
-    public MetaDataMutationResult dropTable(final List<Mutation> 
tableMetaData, final PTableType tableType) throws SQLException {
+    public MetaDataMutationResult dropTable(final List<Mutation> 
tableMetaData, final PTableType tableType, final boolean cascade) throws 
SQLException {
         byte[][] rowKeyMetadata = new byte[3][];
         SchemaUtil.getVarChars(tableMetaData.get(0).getRow(), rowKeyMetadata);
         byte[] tenantIdBytes = 
rowKeyMetadata[PhoenixDatabaseMetaData.TENANT_ID_INDEX];
@@ -1037,7 +1037,7 @@ public class ConnectionQueryServicesImpl extends 
DelegateQueryServices implement
                 new Batch.Call<MetaDataProtocol, MetaDataMutationResult>() {
                     @Override
                     public MetaDataMutationResult call(MetaDataProtocol 
instance) throws IOException {
-                      return instance.dropTable(tableMetaData, 
tableType.getSerializedValue());
+                      return instance.dropTable(tableMetaData, 
tableType.getSerializedValue(), cascade);
                     }
                 });
         

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3af2450d/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionlessQueryServicesImpl.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionlessQueryServicesImpl.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionlessQueryServicesImpl.java
index 6552355..09f42aa 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionlessQueryServicesImpl.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionlessQueryServicesImpl.java
@@ -193,7 +193,7 @@ public class ConnectionlessQueryServicesImpl extends 
DelegateQueryServices imple
     }
 
     @Override
-    public MetaDataMutationResult dropTable(List<Mutation> tableMetadata, 
PTableType tableType) throws SQLException {
+    public MetaDataMutationResult dropTable(List<Mutation> tableMetadata, 
PTableType tableType, boolean cascade) throws SQLException {
         return new MetaDataMutationResult(MutationCode.TABLE_ALREADY_EXISTS, 
0, null);
     }
 

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3af2450d/phoenix-core/src/main/java/org/apache/phoenix/query/DelegateConnectionQueryServices.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/query/DelegateConnectionQueryServices.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/query/DelegateConnectionQueryServices.java
index 306d536..0b6a399 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/query/DelegateConnectionQueryServices.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/query/DelegateConnectionQueryServices.java
@@ -115,8 +115,8 @@ public class DelegateConnectionQueryServices extends 
DelegateQueryServices imple
     }
 
     @Override
-    public MetaDataMutationResult dropTable(List<Mutation> tabeMetaData, 
PTableType tableType) throws SQLException {
-        return getDelegate().dropTable(tabeMetaData, tableType);
+    public MetaDataMutationResult dropTable(List<Mutation> tabeMetaData, 
PTableType tableType, boolean cascade) throws SQLException {
+        return getDelegate().dropTable(tabeMetaData, tableType, cascade);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3af2450d/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java 
b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
index a462a5d..fee329d 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
@@ -1308,17 +1308,17 @@ public class MetaDataClient {
     public MutationState dropTable(DropTableStatement statement) throws 
SQLException {
         String schemaName = statement.getTableName().getSchemaName();
         String tableName = statement.getTableName().getTableName();
-        return dropTable(schemaName, tableName, null, 
statement.getTableType(), statement.ifExists());
+        return dropTable(schemaName, tableName, null, 
statement.getTableType(), statement.ifExists(), statement.cascade());
     }
 
     public MutationState dropIndex(DropIndexStatement statement) throws 
SQLException {
         String schemaName = statement.getTableName().getSchemaName();
         String tableName = statement.getIndexName().getName();
         String parentTableName = statement.getTableName().getTableName();
-        return dropTable(schemaName, tableName, parentTableName, 
PTableType.INDEX, statement.ifExists());
+        return dropTable(schemaName, tableName, parentTableName, 
PTableType.INDEX, statement.ifExists(), false);
     }
 
-    private MutationState dropTable(String schemaName, String tableName, 
String parentTableName, PTableType tableType, boolean ifExists) throws 
SQLException {
+    private MutationState dropTable(String schemaName, String tableName, 
String parentTableName, PTableType tableType, boolean ifExists, boolean 
cascade) throws SQLException {
         connection.rollback();
         boolean wasAutoCommit = connection.getAutoCommit();
         try {
@@ -1338,7 +1338,7 @@ public class MetaDataClient {
                 tableMetaData.add(linkDelete);
             }
 
-            MetaDataMutationResult result = 
connection.getQueryServices().dropTable(tableMetaData, tableType);
+            MetaDataMutationResult result = 
connection.getQueryServices().dropTable(tableMetaData, tableType, cascade);
             MutationCode code = result.getMutationCode();
             switch(code) {
                 case TABLE_NOT_FOUND:

Reply via email to