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: