You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by gj...@apache.org on 2021/04/22 18:25:15 UTC
[phoenix] branch 4.x updated: PHOENIX-6447: Add support for
SYSTEM.CHILD_LINK table in systemcatalogwalentryfilte (#1201)
This is an automated email from the ASF dual-hosted git repository.
gjacoby pushed a commit to branch 4.x
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/4.x by this push:
new 07bf9a6 PHOENIX-6447: Add support for SYSTEM.CHILD_LINK table in systemcatalogwalentryfilte (#1201)
07bf9a6 is described below
commit 07bf9a65566899ebe0bb3e18c3d00ed9a6966fb0
Author: Sandeep Pal <50...@users.noreply.github.com>
AuthorDate: Thu Apr 22 10:47:17 2021 -0700
PHOENIX-6447: Add support for SYSTEM.CHILD_LINK table in systemcatalogwalentryfilte (#1201)
---
.../replication/SystemCatalogWALEntryFilterIT.java | 132 +++++++++++++++++----
.../replication/SystemCatalogWALEntryFilter.java | 78 ++++++++++--
2 files changed, 175 insertions(+), 35 deletions(-)
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/replication/SystemCatalogWALEntryFilterIT.java b/phoenix-core/src/it/java/org/apache/phoenix/replication/SystemCatalogWALEntryFilterIT.java
index 1b5b347..ab6af25 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/replication/SystemCatalogWALEntryFilterIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/replication/SystemCatalogWALEntryFilterIT.java
@@ -31,6 +31,7 @@ import org.apache.phoenix.end2end.ParallelStatsDisabledIT;
import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
import org.apache.phoenix.mapreduce.util.ConnectionUtil;
import org.apache.phoenix.schema.PTable;
+import org.apache.phoenix.util.ByteUtil;
import org.apache.phoenix.util.PhoenixRuntime;
import org.apache.phoenix.util.ReadOnlyProps;
import org.apache.phoenix.util.TestUtil;
@@ -70,7 +71,8 @@ public class SystemCatalogWALEntryFilterIT extends ParallelStatsDisabledIT {
private static final String DROP_NONTENANT_VIEW_SQL = "DROP VIEW IF EXISTS " + NONTENANT_VIEW_NAME;
private static PTable catalogTable;
private static PTable childLinkTable;
- private static WALKey walKey = null;
+ private static WALKey walKeyCatalog = null;
+ private static WALKey walKeyChildLink = null;
private static TableName systemCatalogTableName =
TableName.valueOf(PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME);
private static TableName systemChildLinkTableName =
@@ -89,7 +91,10 @@ public class SystemCatalogWALEntryFilterIT extends ParallelStatsDisabledIT {
connection.createStatement().execute(CREATE_TENANT_VIEW_SQL);
catalogTable = PhoenixRuntime.getTable(connection, PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME);
childLinkTable = PhoenixRuntime.getTable(connection, PhoenixDatabaseMetaData.SYSTEM_CHILD_LINK_NAME);
- walKey = new WALKey(REGION, TableName.valueOf(PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME), 0, 0, uuid);
+ walKeyCatalog = new WALKey(REGION, TableName.valueOf(
+ PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME), 0, 0, uuid);
+ walKeyChildLink = new WALKey(REGION, TableName.valueOf(
+ PhoenixDatabaseMetaData.SYSTEM_CHILD_LINK_NAME), 0, 0, uuid);
};
Assert.assertNotNull(catalogTable);
try (java.sql.Connection connection =
@@ -124,42 +129,88 @@ public class SystemCatalogWALEntryFilterIT extends ParallelStatsDisabledIT {
@Test
public void testSystemCatalogWALEntryFilter() throws Exception {
+ // now create WAL.Entry objects that refer to cells in those view rows in
+ // System.Catalog
+ Get tenantGetCatalog = getGet(catalogTable, TENANT_BYTES, TENANT_VIEW_NAME);
+ Get nonTenantGetCatalog = getGet(catalogTable, DEFAULT_TENANT_BYTES, NONTENANT_VIEW_NAME);
- //now create WAL.Entry objects that refer to cells in those view rows in System.Catalog
-
- Get tenantGet = getGet(catalogTable, TENANT_BYTES, TENANT_VIEW_NAME);
- Get nonTenantGet = getGet(catalogTable, DEFAULT_TENANT_BYTES, NONTENANT_VIEW_NAME);
-
- WAL.Entry nonTenantEntry = getEntry(systemCatalogTableName, nonTenantGet);
- WAL.Entry tenantEntry = getEntry(systemCatalogTableName, tenantGet);
+ WAL.Entry nonTenantEntryCatalog = getEntry(systemCatalogTableName, nonTenantGetCatalog);
+ WAL.Entry tenantEntryCatalog = getEntry(systemCatalogTableName, tenantGetCatalog);
+ int tenantRowCount = getAndAssertTenantCountInEdit(tenantEntryCatalog);
+ Assert.assertTrue(tenantRowCount > 0);
//verify that the tenant view WAL.Entry passes the filter and the non-tenant view does not
SystemCatalogWALEntryFilter filter = new SystemCatalogWALEntryFilter();
// Chain the system catalog WAL entry filter to ChainWALEntryFilter
ChainWALEntryFilter chainWALEntryFilter = new ChainWALEntryFilter(filter);
// Asserting the WALEdit for non tenant has cells before getting filtered
- Assert.assertTrue(nonTenantEntry.getEdit().size() > 0);
+ Assert.assertTrue(nonTenantEntryCatalog.getEdit().size() > 0);
// All the cells will get removed by the filter since they do not belong to tenant
Assert.assertTrue("Non tenant edits for system catalog should not get filtered",
- chainWALEntryFilter.filter(nonTenantEntry).getEdit().isEmpty());
- WAL.Entry filteredTenantEntry = chainWALEntryFilter.filter(tenantEntry);
- Assert.assertNotNull("Tenant view was filtered when it shouldn't be!", filteredTenantEntry);
- Assert.assertEquals("filtered entry is not correct",
- tenantEntry.getEdit().size(), filteredTenantEntry.getEdit().size());
+ chainWALEntryFilter.filter(nonTenantEntryCatalog).getEdit().isEmpty());
+
+ WAL.Entry filteredTenantEntryCatalog = chainWALEntryFilter.filter(tenantEntryCatalog);
+ Assert.assertNotNull("Tenant view was filtered when it shouldn't be!",
+ filteredTenantEntryCatalog);
+ Assert.assertEquals("Not all data for replicated for tenant", tenantRowCount,
+ getAndAssertTenantCountInEdit(filteredTenantEntryCatalog));
//now check that a WAL.Entry with cells from both a tenant and a non-tenant
//catalog row only allow the tenant cells through
WALEdit comboEdit = new WALEdit();
- comboEdit.getCells().addAll(nonTenantEntry.getEdit().getCells());
- comboEdit.getCells().addAll(tenantEntry.getEdit().getCells());
- WAL.Entry comboEntry = new WAL.Entry(walKey, comboEdit);
+ nonTenantEntryCatalog = getEntry(systemCatalogTableName, nonTenantGetCatalog);
+ tenantEntryCatalog = getEntry(systemCatalogTableName, tenantGetCatalog);
+ comboEdit.getCells().addAll(nonTenantEntryCatalog.getEdit().getCells());
+ comboEdit.getCells().addAll(tenantEntryCatalog.getEdit().getCells());
+ WAL.Entry comboEntry = new WAL.Entry(walKeyCatalog, comboEdit);
- Assert.assertEquals(tenantEntry.getEdit().size() + nonTenantEntry.getEdit().size()
+ Assert.assertEquals(tenantEntryCatalog.getEdit().size() + nonTenantEntryCatalog.getEdit().size()
, comboEntry.getEdit().size());
- Assert.assertEquals(tenantEntry.getEdit().size(),
+ Assert.assertEquals(tenantEntryCatalog.getEdit().size(),
chainWALEntryFilter.filter(comboEntry).getEdit().size());
}
+ @Test
+ public void testSystemChildLinkWALEntryFilter() throws Exception {
+ // now create WAL.Entry objects that refer to cells in those view rows in
+ // System.Child_Link
+ Get tenantGetChildLink = getGetChildLink(childLinkTable, TENANT_BYTES, TENANT_VIEW_NAME);
+ Get nonTenantGetChildLink = getGetChildLink(childLinkTable, DEFAULT_TENANT_BYTES, NONTENANT_VIEW_NAME);
+
+ WAL.Entry tenantEntryChildLink = getEntry(systemChildLinkTableName, tenantGetChildLink);
+ WAL.Entry nonTenantEntryChildLink = getEntry(systemChildLinkTableName, nonTenantGetChildLink);
+ int tenantRowCount = getAndAssertTenantCountInEdit(tenantEntryChildLink);
+ Assert.assertTrue(tenantRowCount > 0);
+
+ //verify that the tenant view WAL.Entry passes the filter and the non-tenant view does not
+ SystemCatalogWALEntryFilter filter = new SystemCatalogWALEntryFilter();
+ // Chain the system catalog WAL entry filter to ChainWALEntryFilter
+ ChainWALEntryFilter chainWALEntryFilter = new ChainWALEntryFilter(filter);
+ // Asserting the WALEdit for non tenant has cells before getting filtered
+ Assert.assertTrue(nonTenantEntryChildLink.getEdit().size() > 0);
+ // All the cells will get removed by the filter since they do not belong to tenant
+ Assert.assertTrue("Non tenant edits for system child link should not get filtered",
+ chainWALEntryFilter.filter(nonTenantEntryChildLink).getEdit().isEmpty());
+
+ WAL.Entry filteredTenantEntryChildLink = chainWALEntryFilter.filter(tenantEntryChildLink);
+ Assert.assertNotNull("Tenant view was filtered when it shouldn't be!",
+ filteredTenantEntryChildLink);
+ Assert.assertEquals("Not all data for replicated for tenant", tenantRowCount,
+ getAndAssertTenantCountInEdit(filteredTenantEntryChildLink));
+
+ //now check that a WAL.Entry with cells from both a tenant and a non-tenant
+ // child link row only allow the tenant cells through
+ WALEdit comboEdit = new WALEdit();
+ comboEdit.getCells().addAll(nonTenantEntryChildLink.getEdit().getCells());
+ comboEdit.getCells().addAll(tenantEntryChildLink.getEdit().getCells());
+ WAL.Entry comboEntry = new WAL.Entry(walKeyChildLink, comboEdit);
+
+ Assert.assertEquals(tenantEntryChildLink.getEdit().size() + nonTenantEntryChildLink.getEdit().size()
+ , comboEntry.getEdit().size());
+ Assert.assertEquals(tenantEntryChildLink.getEdit().size(),
+ chainWALEntryFilter.filter(comboEntry).getEdit().size());
+ }
+
public Get getGet(PTable catalogTable, byte[] tenantId, String viewName) {
byte[][] tenantKeyParts = new byte[5][];
tenantKeyParts[0] = tenantId;
@@ -174,6 +225,41 @@ public class SystemCatalogWALEntryFilterIT extends ParallelStatsDisabledIT {
return new Get(key.copyBytes());
}
+ public Get getGetChildLink(PTable catalogTable, byte[] tenantId, String viewName) {
+ byte[][] tenantKeyParts = new byte[5][];
+ tenantKeyParts[0] = ByteUtil.EMPTY_BYTE_ARRAY;
+ tenantKeyParts[1] = ByteUtil.EMPTY_BYTE_ARRAY;
+ tenantKeyParts[2] = Bytes.toBytes(TestUtil.ENTITY_HISTORY_TABLE_NAME);
+ tenantKeyParts[3] = tenantId;
+ tenantKeyParts[4] = Bytes.toBytes(SCHEMA_NAME + "." +viewName.toUpperCase());;
+ ImmutableBytesWritable key = new ImmutableBytesWritable();
+ catalogTable.newKey(key, tenantKeyParts);
+ //the backing byte array of key might have extra space at the end.
+ // need to just slice "the good parts" which we do by calling copyBytes
+ return new Get(key.copyBytes());
+ }
+
+ private boolean isTenantOwnedCell(Cell cell, String tenantId) {
+ String row = Bytes.toString(cell.getRowArray(), cell.getRowOffset(),
+ cell.getRowLength());
+ boolean isTenantIdLeading = row.startsWith(tenantId);
+ boolean isChildLinkForTenantId = row.contains(tenantId)
+ && CellUtil.matchingQualifier(cell,
+ PhoenixDatabaseMetaData.LINK_TYPE_BYTES);
+ return isTenantIdLeading || isChildLinkForTenantId;
+ }
+
+ private int getAndAssertTenantCountInEdit(WAL.Entry entry) {
+ int count = 0;
+ for (Cell cell : entry.getEdit().getCells()) {
+ if (isTenantOwnedCell(cell, TENANT_ID)) {
+ count = count + 1;
+ }
+ }
+ Assert.assertTrue(count > 0);
+ return count;
+ }
+
public WAL.Entry getEntry(TableName tableName, Get get) throws IOException {
WAL.Entry entry = null;
try(Connection conn = ConnectionFactory.createConnection(getUtility().getConfiguration())){
@@ -182,12 +268,14 @@ public class SystemCatalogWALEntryFilterIT extends ParallelStatsDisabledIT {
WALEdit edit = new WALEdit();
if (result != null) {
List<Cell> cellList = result.listCells();
- Assert.assertNotNull("Didn't retrieve any cells from SYSTEM.CATALOG", cellList);
+ Assert.assertNotNull(String.format("Didn't retrieve any cells from table %s",
+ tableName.getNameAsString()), cellList);
for (Cell c : cellList) {
edit.add(c);
}
}
- Assert.assertTrue("Didn't retrieve any cells from SYSTEM.CATALOG", edit.getCells().size() > 0);
+ Assert.assertTrue(String.format("Didn't retrieve any cells from table %s",
+ tableName.getNameAsString()), edit.getCells().size() > 0);
WALKey key = new WALKey(REGION, tableName, 0, 0, uuid);
entry = new WAL.Entry(key, edit);
}
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/replication/SystemCatalogWALEntryFilter.java b/phoenix-core/src/main/java/org/apache/phoenix/replication/SystemCatalogWALEntryFilter.java
index f6929f6..3f29e8e 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/replication/SystemCatalogWALEntryFilter.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/replication/SystemCatalogWALEntryFilter.java
@@ -18,17 +18,23 @@
package org.apache.phoenix.replication;
import org.apache.hadoop.hbase.Cell;
+import org.apache.hadoop.hbase.CellUtil;
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.replication.WALCellFilter;
import org.apache.hadoop.hbase.replication.WALEntryFilter;
import org.apache.hadoop.hbase.wal.WAL;
+import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
import org.apache.phoenix.query.QueryConstants;
+import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.util.SchemaUtil;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.COLUMN_NAME_INDEX;
/**
- * Standard replication of the SYSTEM.CATALOG table can be dangerous because schemas
- * may change between the source and target clusters at different times, in particular
- * during cluster upgrades. However, tenant-owned data such as tenant-owned views need to
- * be copied. This WALEntryFilter will only allow tenant-owned rows in SYSTEM.CATALOG to
+ * Standard replication of the SYSTEM.CATALOG and SYSTEM.CHILD_LINK table can
+ * be dangerous because schemas may change between the source and target
+ * clusters at different times, in particular during cluster upgrades.
+ * However, tenant-owned data such as tenant-owned views need to be copied.
+ * This WALEntryFilter will only allow tenant-owned rows in SYSTEM.CATALOG to
* be replicated. Data from all other tables is automatically passed.
*/
public class SystemCatalogWALEntryFilter implements
@@ -38,6 +44,9 @@ public class SystemCatalogWALEntryFilter implements
* about cell filter for certain WALEdits.
*/
private boolean skipCellFilter;
+ private static final byte[] CHILD_TABLE_BYTES =
+ new byte[]{PTable.LinkType.CHILD_TABLE.getSerializedValue()};
+ private final int NUM_COLUMNS_PRIMARY_KEY = 5;
@Override
public WAL.Entry filter(WAL.Entry entry) {
@@ -45,11 +54,9 @@ public class SystemCatalogWALEntryFilter implements
// should not block anything
// if the WAL.Entry's table isn't System.Catalog or System.Child_Link,
// it auto-passes this filter
- if (!SchemaUtil.isMetaTable(entry.getKey().getTablename().getName())){
- skipCellFilter = true;
- } else {
- skipCellFilter = false;
- }
+ skipCellFilter =
+ !(SchemaUtil.isMetaTable(entry.getKey().getTablename().getName())
+ || SchemaUtil.isChildLinkTable(entry.getKey().getTablename().getName()));
return entry;
}
@@ -58,13 +65,58 @@ public class SystemCatalogWALEntryFilter implements
if (skipCellFilter) {
return cell;
}
- return isTenantRowCell(cell) ? cell : null;
+
+ if (SchemaUtil.isMetaTable(entry.getKey().getTablename().getName())) {
+ return doesKeyHaveLeadingSeparatorByte(cell) ? cell : null;
+ } else {
+ return isTenantRowCellSystemChildLink(cell) ? cell : null;
+ }
}
- private boolean isTenantRowCell(Cell cell) {
- // rows in system.catalog that aren't tenant-owned
+ /**
+ * does the cell key have leading separator byte
+ * @param cell hbase cell
+ * @return true if the cell has leading separator byte
+ */
+ private boolean doesKeyHaveLeadingSeparatorByte(final Cell cell) {
+ // rows in system.catalog or system child that aren't tenant-owned
// will have a leading separator byte
return cell.getRowArray()[cell.getRowOffset()]
- != QueryConstants.SEPARATOR_BYTE;
+ != QueryConstants.SEPARATOR_BYTE;
+ }
+
+ /**
+ * is the cell for system child link a tenant owned. Besides the non empty
+ * tenant id, system.child_link table have tenant owned data for parent child
+ * links. In this case, the column qualifier is
+ * {@code PhoenixDatabaseMetaData#LINK_TYPE_BYTES} and value is
+ * {@code PTable.LinkType.CHILD_TABLE}.
+ * @param cell hbase cell
+ * @return true if the cell is tenant owned
+ */
+ private boolean isTenantRowCellSystemChildLink(final Cell cell) {
+ boolean isTenantRowCell = doesKeyHaveLeadingSeparatorByte(cell);
+
+ ImmutableBytesWritable key = new ImmutableBytesWritable(
+ cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
+ boolean isChildLinkToTenantView = false;
+ if (!isTenantRowCell) {
+ boolean isChildLink = CellUtil.matchingQualifier(
+ cell, PhoenixDatabaseMetaData.LINK_TYPE_BYTES);
+ if (isChildLink) {
+ if (CellUtil.matchingValue(cell, CHILD_TABLE_BYTES)) {
+ byte[][] rowViewKeyMetadata = new byte[NUM_COLUMNS_PRIMARY_KEY][];
+ SchemaUtil.getVarChars(key.get(), key.getOffset(),
+ key.getLength(), 0, rowViewKeyMetadata);
+ // if the child link is to a tenant-owned view,
+ // the COLUMN_NAME field will be the byte[] of the tenant
+ //otherwise, it will be an empty byte array
+ // (NOT QueryConstants.SEPARATOR_BYTE, but a byte[0])
+ isChildLinkToTenantView =
+ rowViewKeyMetadata[COLUMN_NAME_INDEX].length != 0;
+ }
+ }
+ }
+ return isTenantRowCell || isChildLinkToTenantView;
}
}