blob: 2cf3f1a95ce91eebe7b6a4ca0e9c2e3c85f0e59a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.phoenix.end2end.index;
import static org.apache.phoenix.end2end.index.ImmutableIndexIT.verifyRowsForEmptyColValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import com.google.common.collect.Maps;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.CellScanner;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.phoenix.end2end.BaseUniqueNamesOwnClusterIT;
import org.apache.phoenix.end2end.IndexToolIT;
import org.apache.phoenix.hbase.index.IndexRegionObserver;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.util.PhoenixRuntime;
import org.apache.phoenix.util.QueryUtil;
import org.apache.phoenix.util.ReadOnlyProps;
import org.apache.phoenix.util.TestUtil;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import com.google.common.collect.Lists;
@RunWith(Parameterized.class)
public class GlobalIndexCheckerIT extends BaseUniqueNamesOwnClusterIT {
private static final Log LOG = LogFactory.getLog(GlobalIndexCheckerIT.class);
private final boolean async;
private final String tableDDLOptions;
public GlobalIndexCheckerIT(boolean async, boolean encoded) {
this.async = async;
StringBuilder optionBuilder = new StringBuilder();
if (!encoded) {
optionBuilder.append(" COLUMN_ENCODED_BYTES=0 ");
}
this.tableDDLOptions = optionBuilder.toString();
}
@BeforeClass
public static synchronized void doSetup() throws Exception {
Map<String, String> props = Maps.newHashMapWithExpectedSize(1);
setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
}
@Parameters(
name = "async={0},encoded={1}")
public static synchronized Collection<Object[]> data() {
List<Object[]> list = Lists.newArrayListWithExpectedSize(4);
boolean[] Booleans = new boolean[]{true, false};
for (boolean async : Booleans) {
for (boolean encoded : Booleans) {
list.add(new Object[]{async, encoded});
}
}
return list;
}
@After
public void unsetFailForTesting() {
IndexRegionObserver.setFailPreIndexUpdatesForTesting(false);
IndexRegionObserver.setFailDataTableUpdatesForTesting(false);
IndexRegionObserver.setFailPostIndexUpdatesForTesting(false);
}
public static void assertExplainPlan(Connection conn, String selectSql,
String dataTableFullName, String indexTableFullName) throws SQLException {
ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql);
String actualExplainPlan = QueryUtil.getExplainPlan(rs);
IndexToolIT.assertExplainPlan(false, actualExplainPlan, dataTableFullName, indexTableFullName);
}
private void populateTable(String tableName) throws Exception {
Connection conn = DriverManager.getConnection(getUrl());
conn.createStatement().execute("create table " + tableName +
" (id varchar(10) not null primary key, val1 varchar(10), val2 varchar(10), val3 varchar(10))" + tableDDLOptions);
conn.createStatement().execute("upsert into " + tableName + " values ('a', 'ab', 'abc', 'abcd')");
conn.commit();
conn.createStatement().execute("upsert into " + tableName + " values ('b', 'bc', 'bcd', 'bcde')");
conn.commit();
conn.close();
}
@Test
public void testFailPostIndexDeleteUpdate() throws Exception {
String dataTableName = generateUniqueName();
populateTable(dataTableName); // with two rows ('a', 'ab', 'abc', 'abcd') and ('b', 'bc', 'bcd', 'bcde')
try (Connection conn = DriverManager.getConnection(getUrl())) {
String indexTableName = generateUniqueName();
conn.createStatement().execute("CREATE INDEX " + indexTableName + " on " +
dataTableName + " (val1) include (val2, val3)" + (async ? "ASYNC" : ""));
if (async) {
// run the index MR job.
IndexToolIT.runIndexTool(true, false, null, dataTableName, indexTableName);
}
String selectSql = "SELECT id from " + dataTableName + " WHERE val1 = 'ab'";
ResultSet rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("a", rs.getString(1));
assertFalse(rs.next());
// Configure IndexRegionObserver to fail the last write phase (i.e., the post index update phase) where the verify flag is set
// to true and/or index rows are deleted and check that this does not impact the correctness
IndexRegionObserver.setFailPostIndexUpdatesForTesting(true);
String dml = "DELETE from " + dataTableName + " WHERE id = 'a'";
assertEquals(1, conn.createStatement().executeUpdate(dml));
conn.commit();
// The index rows are actually not deleted yet because IndexRegionObserver failed delete operation. However, they are
// made unverified in the pre index update phase (i.e., the first write phase)
dml = "DELETE from " + dataTableName + " WHERE val1 = 'ab'";
// This DML will scan the Index table and detect unverified index rows. This will trigger read repair which
// result in deleting these rows since the corresponding data table rows are deleted already. So, the number of
// rows to be deleted by the "DELETE" DML will be zero since the rows deleted by read repair will not be visible
// to the DML
assertEquals(0, conn.createStatement().executeUpdate(dml));
// Count the number of index rows
String query = "SELECT COUNT(*) from " + indexTableName;
// There should be one row in the index table
rs = conn.createStatement().executeQuery(query);
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
// Add rows and check everything is still okay
verifyTableHealth(conn, dataTableName, indexTableName);
}
}
@Test
public void testPartialRowUpdate() throws Exception {
String dataTableName = generateUniqueName();
populateTable(dataTableName); // with two rows ('a', 'ab', 'abc', 'abcd') and ('b', 'bc', 'bcd', 'bcde')
Connection conn = DriverManager.getConnection(getUrl());
String indexTableName = generateUniqueName();
conn.createStatement().execute("CREATE INDEX " + indexTableName + " on " +
dataTableName + " (val1) include (val2, val3)" + (async ? "ASYNC" : ""));
if (async) {
// run the index MR job.
IndexToolIT.runIndexTool(true, false, null, dataTableName, indexTableName);
}
conn.createStatement().execute("upsert into " + dataTableName + " (id, val2) values ('a', 'abcc')");
conn.commit();
conn.createStatement().execute("upsert into " + dataTableName + " (id, val2) values ('c', 'cde')");
conn.commit();
String selectSql = "SELECT * from " + dataTableName + " WHERE val1 = 'ab'";
// Verify that we will read from the index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName);
ResultSet rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("a", rs.getString(1));
assertEquals("ab", rs.getString(2));
assertEquals("abcc", rs.getString(3));
assertEquals("abcd", rs.getString(4));
assertFalse(rs.next());
conn.createStatement().execute("upsert into " + dataTableName + " (id, val1, val3) values ('a', 'ab', 'abcdd')");
conn.commit();
// Verify that we will read from the index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName);
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("a", rs.getString(1));
assertEquals("ab", rs.getString(2));
assertEquals("abcc", rs.getString(3));
assertEquals("abcdd", rs.getString(4));
assertFalse(rs.next());
conn.close();
}
@Test
public void testFailPreIndexRowUpdate() throws Exception {
String dataTableName = generateUniqueName();
populateTable(dataTableName); // with two rows ('a', 'ab', 'abc', 'abcd') and ('b', 'bc', 'bcd', 'bcde')
try (Connection conn = DriverManager.getConnection(getUrl())) {
String indexTableName = generateUniqueName();
conn.createStatement().execute("CREATE INDEX " + indexTableName + " on " +
dataTableName + " (val1) include (val2, val3)" + (async ? "ASYNC" : ""));
if (async) {
// run the index MR job.
IndexToolIT.runIndexTool(true, false, null, dataTableName, indexTableName);
}
// Configure IndexRegionObserver to fail the first write phase (i.e., the pre index update phase). This should not
// lead to any change on index or data table rows
IndexRegionObserver.setFailPreIndexUpdatesForTesting(true);
conn.createStatement().execute("upsert into " + dataTableName + " (id, val2) values ('a', 'abcc')");
commitWithException(conn);
conn.createStatement().execute("upsert into " + dataTableName + " (id, val1, val2) values ('c', 'cd','cde')");
commitWithException(conn);
IndexRegionObserver.setFailPreIndexUpdatesForTesting(false);
String selectSql = "SELECT val2, val3 from " + dataTableName + " WHERE val1 = 'ab'";
// Verify that we will read from the index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName);
ResultSet rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("abc", rs.getString(1));
assertEquals("abcd", rs.getString(2));
assertFalse(rs.next());
// Add rows and check everything is still okay
verifyTableHealth(conn, dataTableName, indexTableName);
}
}
@Test
public void testFailPostIndexRowUpdate() throws Exception {
String dataTableName = generateUniqueName();
populateTable(dataTableName); // with two rows ('a', 'ab', 'abc', 'abcd') and ('b', 'bc', 'bcd', 'bcde')
try (Connection conn = DriverManager.getConnection(getUrl())) {
String indexTableName = generateUniqueName();
conn.createStatement().execute("CREATE INDEX " + indexTableName + " on " +
dataTableName + " (val1) include (val2, val3)" + (async ? "ASYNC" : ""));
if (async) {
// run the index MR job.
IndexToolIT.runIndexTool(true, false, null, dataTableName, indexTableName);
}
// Configure IndexRegionObserver to fail the last write phase (i.e., the post index update phase) where the verify flag is set
// to true and/or index rows are deleted and check that this does not impact the correctness
IndexRegionObserver.setFailPostIndexUpdatesForTesting(true);
conn.createStatement().execute("upsert into " + dataTableName + " (id, val2) values ('a', 'abcc')");
conn.commit();
conn.createStatement().execute("upsert into " + dataTableName + " (id, val1, val2) values ('c', 'cd','cde')");
conn.commit();
IndexRegionObserver.setFailPostIndexUpdatesForTesting(false);
String selectSql = "SELECT val2, val3 from " + dataTableName + " WHERE val1 = 'ab'";
// Verify that we will read from the index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName);
ResultSet rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("abcc", rs.getString(1));
assertEquals("abcd", rs.getString(2));
assertFalse(rs.next());
// Add rows and check everything is still okay
verifyTableHealth(conn, dataTableName, indexTableName);
}
}
public static void checkUnverifiedCellCount(Connection conn, String indexTableName) throws Exception {
Table hIndexTable = conn.unwrap(PhoenixConnection.class).getQueryServices()
.getTable(Bytes.toBytes(indexTableName));
long indexCnt = TestUtil.getRowCount(hIndexTable, false);
assertEquals(1, indexCnt);
assertEquals(true, verifyRowsForEmptyColValue(conn, indexTableName, IndexRegionObserver.UNVERIFIED_BYTES));
Scan s = new Scan();
int cntCellValues = 0;
try (ResultScanner scanner = hIndexTable.getScanner(s)) {
Result result;
while ((result = scanner.next()) != null) {
CellScanner cellScanner = result.cellScanner();
while (cellScanner.advance()) {
cntCellValues++;
}
}
}
assertEquals(1, cntCellValues);
}
@Test
public void testUnverifiedRowIncludesOnlyEmptyCell() throws Exception {
String dataTableName = generateUniqueName();
try (Connection conn = DriverManager.getConnection(getUrl())) {
conn.createStatement().execute("create table " + dataTableName +
" (id varchar(10) not null primary key, val1 varchar(10), val2 varchar(10), val3 varchar(10))" + tableDDLOptions);
String indexTableName = generateUniqueName();
conn.createStatement().execute("CREATE INDEX " + indexTableName + " on " +
dataTableName + " (val1) include (val2, val3)");
// Configure IndexRegionObserver to fail the last write phase (i.e., the post index update phase)
IndexRegionObserver.setFailPostIndexUpdatesForTesting(true);
conn.createStatement().execute("upsert into " + dataTableName + " (id, val2) values ('a', 'abcc')");
conn.commit();
IndexRegionObserver.setFailPostIndexUpdatesForTesting(false);
// check that in the first phase we don't send the full row.
// We count the num of cells for this
checkUnverifiedCellCount(conn, indexTableName);
// Add rows and check everything is still okay
verifyTableHealth(conn, dataTableName, indexTableName);
}
}
@Test
public void testOnePhaseOverwiteFollowingTwoPhaseWrite() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl())) {
String dataTableName = generateUniqueName();
populateTable(dataTableName); // with two rows ('a', 'ab', 'abc', 'abcd') and ('b', 'bc', 'bcd', 'bcde')
String indexTableName = generateUniqueName();
conn.createStatement().execute("CREATE INDEX " + indexTableName + "1 on " +
dataTableName + " (val1) include (val2, val3)" + (async ? "ASYNC" : ""));
conn.createStatement().execute("CREATE INDEX " + indexTableName + "2 on " +
dataTableName + " (val2) include (val1, val3)" + (async ? "ASYNC" : ""));
if (async) {
// run the index MR job.
IndexToolIT.runIndexTool(true, false, null, dataTableName, indexTableName + "1");
IndexToolIT.runIndexTool(true, false, null, dataTableName, indexTableName + "2");
}
// Two Phase write. This write is recoverable
IndexRegionObserver.setFailPostIndexUpdatesForTesting(true);
conn.createStatement().execute("upsert into " + dataTableName + " values ('c', 'cd', 'cde', 'cdef')");
conn.commit();
// One Phase write. This write is not recoverable
IndexRegionObserver.setFailDataTableUpdatesForTesting(true);
conn.createStatement().execute("upsert into " + dataTableName + " values ('c', 'cd', 'cdee', 'cdfg')");
commitWithException(conn);
// Let three phase writes happen as in the normal case
IndexRegionObserver.setFailDataTableUpdatesForTesting(false);
IndexRegionObserver.setFailPostIndexUpdatesForTesting(false);
String selectSql = "SELECT val2, val3 from " + dataTableName + " WHERE val1 = 'cd'";
// Verify that we will read from the first index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName + "1");
// Verify the first write is visible but the second one is not
ResultSet rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("cde", rs.getString(1));
assertEquals("cdef", rs.getString(2));
assertFalse(rs.next());
// Add rows and check everything is still okay
verifyTableHealth(conn, dataTableName, indexTableName);
}
}
@Test
public void testOnePhaseOverwrite() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl())) {
String dataTableName = generateUniqueName();
populateTable(dataTableName); // with two rows ('a', 'ab', 'abc', 'abcd') and ('b', 'bc', 'bcd', 'bcde')
String indexTableName = generateUniqueName();
conn.createStatement().execute("CREATE INDEX " + indexTableName + "1 on " +
dataTableName + " (val1) include (val2, val3)" + (async ? "ASYNC" : ""));
conn.createStatement().execute("CREATE INDEX " + indexTableName + "2 on " +
dataTableName + " (val2) include (val1, val3)" + (async ? "ASYNC" : ""));
if (async) {
// run the index MR job.
IndexToolIT.runIndexTool(true, false, null, dataTableName, indexTableName + "1");
IndexToolIT.runIndexTool(true, false, null, dataTableName, indexTableName + "2");
}
// Configure IndexRegionObserver to skip the last two write phase (i.e., the data table update and post index
// update phase) and check that this does not impact the correctness (one overwrite)
IndexRegionObserver.setFailDataTableUpdatesForTesting(true);
IndexRegionObserver.setFailPostIndexUpdatesForTesting(true);
conn.createStatement().execute("upsert into " + dataTableName + " (id, val2) values ('a', 'abcc')");
commitWithException(conn);
IndexRegionObserver.setFailDataTableUpdatesForTesting(false);
IndexRegionObserver.setFailPostIndexUpdatesForTesting(false);
String selectSql = "SELECT val2, val3 from " + dataTableName + " WHERE val1 = 'ab'";
// Verify that we will read from the first index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName + "1");
// Verify that one phase write has no effect
ResultSet rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("abc", rs.getString(1));
assertEquals("abcd", rs.getString(2));
assertFalse(rs.next());
selectSql = "SELECT val2, val3 from " + dataTableName + " WHERE val2 = 'abcc'";
// Verify that we will read from the second index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName + "2");
rs = conn.createStatement().executeQuery(selectSql);
// Verify that one phase writes have no effect
assertFalse(rs.next());
// Configure IndexRegionObserver to skip the last two write phase (i.e., the data table update and post index
// update phase) and check that this does not impact the correctness (two overwrites)
IndexRegionObserver.setFailDataTableUpdatesForTesting(true);
IndexRegionObserver.setFailPostIndexUpdatesForTesting(true);
conn.createStatement().execute("upsert into " + dataTableName + " (id, val2) values ('a', 'abccc')");
commitWithException(conn);
conn.createStatement().execute("upsert into " + dataTableName + " (id, val2) values ('a', 'abcccc')");
commitWithException(conn);
IndexRegionObserver.setFailDataTableUpdatesForTesting(false);
IndexRegionObserver.setFailPostIndexUpdatesForTesting(false);
selectSql = "SELECT val2, val3 from " + dataTableName + " WHERE val1 = 'ab'";
// Verify that we will read from the first index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName + "1");
// Verify that one phase writes have no effect
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("abc", rs.getString(1));
assertEquals("abcd", rs.getString(2));
assertFalse(rs.next());
selectSql = "SELECT val2, val3 from " + dataTableName + " WHERE val2 = 'abccc'";
// Verify that we will read from the second index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName + "2");
rs = conn.createStatement().executeQuery(selectSql);
// Verify that one phase writes have no effect
assertFalse(rs.next());
selectSql = "SELECT val2, val3 from " + dataTableName + " WHERE val2 = 'abcccc'";
// Verify that we will read from the second index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName + "2");
rs = conn.createStatement().executeQuery(selectSql);
// Verify that one phase writes have no effect
assertFalse(rs.next());
// Add rows and check everything is still okay
verifyTableHealth(conn, dataTableName, indexTableName);
}
}
@Test
public void testFailDataTableAndPostIndexRowUpdate() throws Exception {
String dataTableName = generateUniqueName();
populateTable(dataTableName); // with two rows ('a', 'ab', 'abc', 'abcd') and ('b', 'bc', 'bcd', 'bcde')
try (Connection conn = DriverManager.getConnection(getUrl())) {
String indexName = generateUniqueName();
conn.createStatement().execute("CREATE INDEX " + indexName + "1 on " +
dataTableName + " (val1) include (val2, val3)" + (async ? "ASYNC" : ""));
conn.createStatement().execute("CREATE INDEX " + indexName + "2 on " +
dataTableName + " (val2) include (val1, val3)" + (async ? "ASYNC" : ""));
if (async) {
// run the index MR job.
IndexToolIT.runIndexTool(true, false, null, dataTableName, indexName + "1");
IndexToolIT.runIndexTool(true, false, null, dataTableName, indexName + "2");
}
// Configure IndexRegionObserver to fail the last two write phase (i.e., the data table update and post index update phase)
// and check that this does not impact the correctness
IndexRegionObserver.setFailDataTableUpdatesForTesting(true);
IndexRegionObserver.setFailPostIndexUpdatesForTesting(true);
conn.createStatement().execute("upsert into " + dataTableName + " (id, val2) values ('a', 'abcc')");
commitWithException(conn);
IndexRegionObserver.setFailDataTableUpdatesForTesting(false);
IndexRegionObserver.setFailPostIndexUpdatesForTesting(false);
conn.createStatement().execute("upsert into " + dataTableName + " (id, val3) values ('a', 'abcdd')");
conn.commit();
String selectSql = "SELECT val2, val3 from " + dataTableName + " WHERE val1 = 'ab'";
// Verify that we will read from the first index table
assertExplainPlan(conn, selectSql, dataTableName, indexName + "1");
ResultSet rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("abc", rs.getString(1));
assertEquals("abcdd", rs.getString(2));
assertFalse(rs.next());
selectSql = "SELECT val2, val3 from " + dataTableName + " WHERE val2 = 'abc'";
// Verify that we will read from the second index table
assertExplainPlan(conn, selectSql, dataTableName, indexName + "2");
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("abc", rs.getString(1));
assertEquals("abcdd", rs.getString(2));
assertFalse(rs.next());
// Add rows and check everything is still okay
verifyTableHealth(conn, dataTableName, indexName);
}
}
@Test
public void testViewIndexRowUpdate() throws Exception {
if (async) {
// No need to run the same test twice one for async = true and the other for async = false
return;
}
try (Connection conn = DriverManager.getConnection(getUrl())) {
// Create a base table
String dataTableName = generateUniqueName();
conn.createStatement().execute("create table " + dataTableName +
" (oid varchar(10) not null, kp char(3) not null, val1 varchar(10)" +
"CONSTRAINT pk PRIMARY KEY (oid, kp)) COLUMN_ENCODED_BYTES=0, MULTI_TENANT=true");
// Create a view on the base table
String viewName = generateUniqueName();
conn.createStatement().execute("CREATE VIEW " + viewName + " (id char(10) not null, " +
"val2 varchar, val3 varchar, CONSTRAINT pk PRIMARY KEY (id)) AS SELECT * FROM " + dataTableName +
" WHERE kp = '0EC'");
// Create an index on the view
String indexName = generateUniqueName();
conn.createStatement().execute("CREATE INDEX " + indexName + " on " +
viewName + " (val2) include (val3)");
Properties props = new Properties();
props.setProperty(PhoenixRuntime.TENANT_ID_ATTRIB, "o1");
try (Connection tenantConn = DriverManager.getConnection(getUrl(), props)) {
// Configure IndexRegionObserver to fail the last write phase (i.e., the post index update phase)
// This will leave index rows unverified
// Create a view of the view
String childViewName = generateUniqueName();
tenantConn.createStatement().execute("CREATE VIEW " + childViewName + " (zid CHAR(15)) " +
"AS SELECT * FROM " + viewName);
// Create another view of the child view
String grandChildViewName = generateUniqueName();
tenantConn.createStatement().execute("CREATE VIEW " + grandChildViewName + " (val4 CHAR(15)) " +
"AS SELECT * FROM " + childViewName);
IndexRegionObserver.setFailPostIndexUpdatesForTesting(true);
tenantConn.createStatement().execute("upsert into " + childViewName + " (zid, id, val1, val2, val3) VALUES('z1','1', 'a1','b1','c1')");
tenantConn.commit();
IndexRegionObserver.setFailPostIndexUpdatesForTesting(false);
// Activate read repair
String selectSql = "select val3 from " + childViewName + " WHERE val2 = 'b1'";
ResultSet rs = tenantConn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("c1", rs.getString("val3"));
// Configure IndexRegionObserver to fail the last write phase (i.e., the post index update phase)
// This will leave index rows unverified
IndexRegionObserver.setFailPostIndexUpdatesForTesting(true);
tenantConn.createStatement().execute("upsert into " + grandChildViewName + " (zid, id, val2, val3, val4) VALUES('z1', '2', 'b2', 'c2', 'd1')");
tenantConn.commit();
IndexRegionObserver.setFailPostIndexUpdatesForTesting(false);
// Activate read repair
selectSql = "select id, val3 from " + grandChildViewName + " WHERE val2 = 'b2'";
rs = tenantConn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("2", rs.getString("id"));
assertEquals("c2", rs.getString("val3"));
}
}
}
static private void commitWithException(Connection conn) {
try {
conn.commit();
IndexRegionObserver.setFailPreIndexUpdatesForTesting(false);
IndexRegionObserver.setFailDataTableUpdatesForTesting(false);
IndexRegionObserver.setFailPostIndexUpdatesForTesting(false);
fail();
} catch (Exception e) {
// this is expected
}
}
static private void verifyTableHealth(Connection conn, String dataTableName, String indexTableName) throws Exception {
// Add two rows and check everything is still okay
conn.createStatement().execute("upsert into " + dataTableName + " values ('a', 'ab', 'abc', 'abcd')");
conn.createStatement().execute("upsert into " + dataTableName + " values ('z', 'za', 'zab', 'zabc')");
conn.commit();
String selectSql = "SELECT * from " + dataTableName + " WHERE val1 = 'ab'";
///Verify that we will read from the index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName);
ResultSet rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("a", rs.getString(1));
assertEquals("ab", rs.getString(2));
assertEquals("abc", rs.getString(3));
assertEquals("abcd", rs.getString(4));
assertFalse(rs.next());
selectSql = "SELECT * from " + dataTableName + " WHERE val1 = 'za'";
///Verify that we will read from the index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName);
rs = conn.createStatement().executeQuery(selectSql);
conn.commit();
assertTrue(rs.next());
assertEquals("z", rs.getString(1));
assertEquals("za", rs.getString(2));
assertEquals("zab", rs.getString(3));
assertEquals("zabc", rs.getString(4));
assertFalse(rs.next());
}
}