blob: 50bc677d86848f46248ce3f0e31b497e3d279983 [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 org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.mapreduce.CounterGroup;
import org.apache.phoenix.end2end.IndexToolIT;
import org.apache.phoenix.end2end.NeedsOwnMiniClusterTest;
import org.apache.phoenix.exception.PhoenixParserException;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixResultSet;
import org.apache.phoenix.mapreduce.index.IndexTool;
import org.apache.phoenix.query.BaseTest;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.schema.ColumnNotFoundException;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
import org.apache.phoenix.util.EnvironmentEdgeManager;
import org.apache.phoenix.util.PhoenixRuntime;
import org.apache.phoenix.util.PropertiesUtil;
import org.apache.phoenix.util.QueryUtil;
import org.apache.phoenix.util.ReadOnlyProps;
import org.junit.After;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Map;
import java.util.Properties;
import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REBUILD_BEYOND_MAXLOOKBACK_INVALID_INDEX_ROW_COUNT;
import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REBUILD_BEYOND_MAXLOOKBACK_MISSING_INDEX_ROW_COUNT;
import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REBUILD_INVALID_INDEX_ROW_COUNT;
import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REBUILD_MISSING_INDEX_ROW_COUNT;
import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REBUILD_OLD_INDEX_ROW_COUNT;
import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REBUILD_UNKNOWN_INDEX_ROW_COUNT;
import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REPAIR_EXTRA_UNVERIFIED_INDEX_ROW_COUNT;
import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REPAIR_EXTRA_VERIFIED_INDEX_ROW_COUNT;
import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.REBUILT_INDEX_ROW_COUNT;
import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@Category(NeedsOwnMiniClusterTest.class)
@RunWith(Parameterized.class)
public class PartialIndexIT extends BaseTest {
private final boolean local;
private final boolean uncovered;
private final boolean salted;
public PartialIndexIT (boolean local, boolean uncovered, boolean salted) {
this.local = local;
this.uncovered = uncovered;
this.salted = salted;
}
@BeforeClass
public static synchronized void doSetup() throws Exception {
Map<String, String> props = Maps.newHashMapWithExpectedSize(1);
props.put(QueryServices.GLOBAL_INDEX_ROW_AGE_THRESHOLD_TO_DELETE_MS_ATTRIB, Long.toString(0));
setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
}
@After
public void unsetFailForTesting() throws Exception {
boolean refCountLeaked = isAnyStoreRefCountLeaked();
assertFalse("refCount leaked", refCountLeaked);
}
@Parameterized.Parameters(
name = "local={0}, uncovered={1}, salted={2}")
public static synchronized Collection<Boolean[]> data() {
return Arrays.asList(new Boolean[][] {
// Partial local indexes are not supported currently.
{false, false, true},
{false, false, false},
{false, true, false},
{false, true, true}
});
}
public static void assertPlan(PhoenixResultSet rs, String schemaName, String tableName) {
PTable table = rs.getContext().getCurrentTable().getTable();
assertTrue(table.getSchemaName().getString().equals(schemaName) &&
table.getTableName().getString().equals(tableName));
}
private static void verifyIndex(String dataTableName, String indexTableName) throws Exception {
IndexTool indexTool = IndexToolIT.runIndexTool(false, "", dataTableName,
indexTableName, null, 0, IndexTool.IndexVerifyType.ONLY);
assertEquals(0, indexTool.getJob().getCounters().
findCounter(REBUILT_INDEX_ROW_COUNT).getValue());
assertEquals(0, indexTool.getJob().getCounters().
findCounter(BEFORE_REBUILD_INVALID_INDEX_ROW_COUNT).getValue());
assertEquals(0, indexTool.getJob().getCounters().
findCounter(BEFORE_REBUILD_MISSING_INDEX_ROW_COUNT).getValue());
assertEquals(0, indexTool.getJob().getCounters().
findCounter(BEFORE_REBUILD_BEYOND_MAXLOOKBACK_MISSING_INDEX_ROW_COUNT).getValue());
assertEquals(0, indexTool.getJob().getCounters().
findCounter(BEFORE_REBUILD_BEYOND_MAXLOOKBACK_INVALID_INDEX_ROW_COUNT).getValue());
assertEquals(0, indexTool.getJob().getCounters().
findCounter(BEFORE_REBUILD_OLD_INDEX_ROW_COUNT).getValue());
assertEquals(0, indexTool.getJob().getCounters().
findCounter(BEFORE_REBUILD_UNKNOWN_INDEX_ROW_COUNT).getValue());
IndexToolIT.runIndexTool(false, "", dataTableName,
indexTableName, null, 0, IndexTool.IndexVerifyType.ONLY, "-fi");
CounterGroup mrJobCounters = IndexToolIT.getMRJobCounters(indexTool);
assertEquals(0,
mrJobCounters.findCounter(BEFORE_REBUILD_INVALID_INDEX_ROW_COUNT.name()).getValue());
assertEquals(0,
mrJobCounters.findCounter(BEFORE_REPAIR_EXTRA_VERIFIED_INDEX_ROW_COUNT.name()).getValue());
assertEquals(0,
mrJobCounters.findCounter(BEFORE_REPAIR_EXTRA_UNVERIFIED_INDEX_ROW_COUNT.name()).getValue());
}
@Test
public void testUnsupportedDDLs() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl())) {
String dataTableName = generateUniqueName();
conn.createStatement().execute(
"create table " + dataTableName + " (id varchar not null primary key, "
+ "A integer, B integer, C double, D varchar)" + (salted ?
" SALT_BUCKETS=4" :
""));
String indexTableName = generateUniqueName();
try {
conn.createStatement().execute(
"CREATE " + (uncovered ? "UNCOVERED " : " ") + (local ? "LOCAL " : " ")
+ "INDEX " + indexTableName + " on " + dataTableName + " (A) "
+ (uncovered ? "" : "INCLUDE (B, C, D)") + " WHERE E > 50 ASYNC");
Assert.fail();
} catch (ColumnNotFoundException e) {
// Expected
}
try {
conn.createStatement().execute(
"CREATE " + (uncovered ? "UNCOVERED " : " ") + (local ? "LOCAL " : " ")
+ "INDEX " + indexTableName + " on " + dataTableName + " (A) "
+ (uncovered ? "" : "INCLUDE (B, C, D)")
+ " WHERE A < ANY (SELECT B FROM " + dataTableName + ")");
Assert.fail();
} catch (SQLException e) {
// Index where clause cannot include a subquery
assertTrue(e.getSQLState().equals("23101"));
}
try {
conn.createStatement().execute(
"CREATE LOCAL INDEX " + indexTableName + " on " + dataTableName + " (A)"
+ " WHERE A > 0");
Assert.fail();
} catch (PhoenixParserException e) {
// Local indexes are not supported yet
}
}
}
@Test
public void testDDLWithAllDataTypes() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl())) {
String dataTableName = generateUniqueName();
String fullTableName = String.format("%s.%s", "S", dataTableName);
conn.createStatement().execute(
"create table " + fullTableName
+ " (id varchar not null, kp varchar not null, "
+ "A INTEGER, B UNSIGNED_INT, C BIGINT, D UNSIGNED_LONG, E TINYINT, "
+ "F UNSIGNED_TINYINT, G SMALLINT, H UNSIGNED_SMALLINT, I FLOAT, "
+ "J UNSIGNED_FLOAT, K DOUBLE, L UNSIGNED_DOUBLE, M DECIMAL, "
+ "N BOOLEAN, O TIME, P DATE, Q TIMESTAMP, R UNSIGNED_TIME, "
+ "S UNSIGNED_DATE, T UNSIGNED_TIMESTAMP, U CHAR(10), V BINARY(1024), "
+ "W VARBINARY, Y INTEGER ARRAY, Z VARCHAR ARRAY[10], AA DATE ARRAY, "
+ "AB TIMESTAMP ARRAY, AC UNSIGNED_TIME ARRAY, AD UNSIGNED_DATE ARRAY, "
+ "AE UNSIGNED_TIMESTAMP ARRAY, AF JSON "
+ "CONSTRAINT pk PRIMARY KEY (id,kp)) "
+ "MULTI_TENANT=true, COLUMN_ENCODED_BYTES=0" );
String indexTableName = generateUniqueName();
try {
conn.createStatement().execute(
"CREATE " + (uncovered ? "UNCOVERED " : " ") + (local ? "LOCAL " : " ")
+ "INDEX IF NOT EXISTS " + indexTableName + " on " + fullTableName
+ " (kp,A) WHERE (kp > '5')");
} catch (PhoenixParserException e) {
e.printStackTrace();
Assert.fail();
}
}
}
@Test
public void testAtomicUpsert() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl())) {
String dataTableName = generateUniqueName();
conn.createStatement().execute("create table " + dataTableName +
" (id varchar not null primary key, " +
"A integer, B integer, C double, D varchar)" +
(salted ? " SALT_BUCKETS=4" : ""));
String indexTableName = generateUniqueName();
// Add rows to the data table before creating a partial index to test that the index
// will be built correctly by IndexTool
conn.createStatement().execute("upsert into " + dataTableName +
" values ('id1', 25, 2, 3.14, 'a')");
conn.createStatement().execute("upsert into " + dataTableName +
" (id, A, D) values ('id2', 100, 'b')");
conn.commit();
conn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") +
(local ? "LOCAL " : " ") +"INDEX "
+ indexTableName + " on " + dataTableName + " (A) " +
(uncovered ? "" : "INCLUDE (B, C, D)") + " WHERE A > 50 ASYNC");
IndexToolIT.runIndexTool(false, null, dataTableName, indexTableName);
String selectSql = "SELECT D from " + dataTableName + " WHERE A > 60";
ResultSet rs = conn.createStatement().executeQuery(selectSql);
// Verify that the index table is used
assertPlan((PhoenixResultSet) rs, "", indexTableName);
assertTrue(rs.next());
assertEquals("b", rs.getString(1));
assertFalse(rs.next());
//explain plan verify to check if partial index is used
rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql);
assertTrue(rs.next());
assertTrue(rs.getString(1).contains(indexTableName));
selectSql = "SELECT D from " + dataTableName + " WHERE A = 50";
rs = conn.createStatement().executeQuery(selectSql);
// Verify that the index table is not used
assertPlan((PhoenixResultSet) rs, "", dataTableName);
//explain plan verify to check if partial index is not used
rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql);
assertTrue(rs.next());
assertFalse(rs.getString(1).contains(indexTableName));
// Add more rows to test the index write path
conn.createStatement().execute("upsert into " + dataTableName +
" values ('id3', 50, 2, 9.5, 'c')");
conn.commit();
conn.createStatement().execute("upsert into " + dataTableName +
" values ('id4', 75, 2, 9.5, 'd')");
conn.commit();
// Verify that index table includes only the rows with A > 50
selectSql = "SELECT * from " + indexTableName;
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals(75, rs.getInt(1));
assertTrue(rs.next());
assertEquals(100, rs.getInt(1));
assertFalse(rs.next());
// Overwrite an existing row that satisfies the index WHERE clause using an atomic
// upsert such that the new version of the row does not satisfy the index where clause
// anymore. This should result in deleting the index row.
String dml = "UPSERT INTO " + dataTableName + " values ('id2', 300, 2, 9.5, 'd') " +
"ON DUPLICATE KEY UPDATE A = 0";
conn.createStatement().execute(dml);
conn.commit();
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals(75, rs.getInt(1));
assertFalse(rs.next());
// Retrieve update row from the data table and verify that the index table is not used
selectSql = "SELECT ID from " + dataTableName + " WHERE A = 0";
rs = conn.createStatement().executeQuery(selectSql);
assertPlan((PhoenixResultSet) rs, "", dataTableName);
assertTrue(rs.next());
assertEquals("id2", rs.getString(1));
//explain plan verify to check if partial index is not used
rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql);
assertTrue(rs.next());
assertFalse(rs.getString(1).contains(indexTableName));
// Test index verification and repair by IndexTool
verifyIndex(dataTableName, indexTableName);
try (PhoenixConnection newConn = (PhoenixConnection) DriverManager.getConnection(getUrl())) {
PTable indexTable = newConn.getTableNoCache(indexTableName);
assertTrue(indexTable.getIndexWhere().equals("A > 50"));
}
}
}
@Test
public void testComparisonOfColumns() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl())) {
String dataTableName = generateUniqueName();
conn.createStatement().execute("create table " + dataTableName +
" (id varchar not null primary key, " +
"A integer, B integer, C double, D varchar) COLUMN_ENCODED_BYTES=0" +
(salted ? ", SALT_BUCKETS=4" : ""));
String indexTableName = generateUniqueName();
// Add rows to the data table before creating a partial index to test that the index
// will be built correctly by IndexTool
conn.createStatement().execute("upsert into " + dataTableName +
" values ('id1', 25, 2, 3.14, 'a')");
conn.commit();
conn.createStatement().execute("upsert into " + dataTableName +
" (id, A, B, D) values ('id2', 100, 200, 'b')");
conn.commit();
conn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") +
(local ? "LOCAL " : " ") +"INDEX "
+ indexTableName + " on " + dataTableName + " (A) " +
(uncovered ? "" : "INCLUDE (B, C, D)") + " WHERE A > B ASYNC");
conn.commit();
IndexToolIT.runIndexTool(false, null, dataTableName, indexTableName);
String selectSql = "SELECT D from " + dataTableName + " WHERE A > B and D is not NULL";
ResultSet rs = conn.createStatement().executeQuery(selectSql);
// Verify that the index table is used
assertPlan((PhoenixResultSet) rs, "", indexTableName);
assertTrue(rs.next());
assertEquals("a", rs.getString(1));
assertFalse(rs.next());
//explain plan verify to check if partial index is used
rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql);
assertTrue(rs.next());
assertTrue(rs.getString(1).contains(indexTableName));
selectSql = "SELECT D from " + dataTableName + " WHERE A > 100";
rs = conn.createStatement().executeQuery(selectSql);
// Verify that the index table is not used
assertPlan((PhoenixResultSet) rs, "", dataTableName);
//explain plan verify to check if partial index is not used
rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql);
assertTrue(rs.next());
assertFalse(rs.getString(1).contains(indexTableName));
// Add more rows to test the index write path
conn.createStatement().execute("upsert into " + dataTableName +
" values ('id3', 50, 300, 9.5, 'c')");
conn.commit();
conn.createStatement().execute("upsert into " + dataTableName +
" values ('id4', 75, 2, 9.5, 'd')");
conn.commit();
conn.createStatement().execute("upsert into " + dataTableName +
" values ('id4', 76, 2, 9.5, 'd')");
conn.commit();
// Verify that index table includes only the rows with A > B
selectSql = "SELECT * from " + indexTableName;
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals(25, rs.getInt(1));
assertTrue(rs.next());
assertEquals(76, rs.getInt(1));
assertFalse(rs.next());
// Overwrite an existing row that satisfies the index WHERE clause such that
// the new version of the row does not satisfy the index where clause anymore. This
// should result in deleting the index row.
conn.createStatement().execute("upsert into " + dataTableName +
" (ID, B) values ('id1', 100)");
conn.commit();
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals(76, rs.getInt(1));
assertFalse(rs.next());
// Test index verification and repair by IndexTool
verifyIndex(dataTableName, indexTableName);
try (PhoenixConnection newConn = (PhoenixConnection) DriverManager.getConnection(getUrl())) {
PTable indexTable = newConn.getTableNoCache(indexTableName);
assertTrue(indexTable.getIndexWhere().equals("A > B"));
}
}
}
@Test
public void testIsNull() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl())) {
String dataTableName = generateUniqueName();
conn.createStatement().execute("create table " + dataTableName +
" (id varchar not null primary key, " +
"A integer, B integer, C double, D varchar)" +
(salted ? " SALT_BUCKETS=4" : ""));
String indexTableName = generateUniqueName();
// Add rows to the data table before creating a partial index to test that the index
// will be built correctly by IndexTool
conn.createStatement().execute("upsert into " + dataTableName +
" values ('id1', 70, 2, 3.14, 'a')");
conn.commit();
conn.createStatement().execute("upsert into " + dataTableName +
" (id, A, D) values ('id2', 100, 'b')");
conn.commit();
conn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") +
(local ? "LOCAL " : " ") +"INDEX "
+ indexTableName + " on " + dataTableName + " (A) " +
(uncovered ? "" : "INCLUDE (B, C, D)") + " WHERE B IS NOT NULL AND " +
"C IS NOT NULL ASYNC");
IndexToolIT.runIndexTool(false, null, dataTableName, indexTableName);
String selectSql = "SELECT A, D from " + dataTableName +
" WHERE A > 60 AND B IS NOT NULL AND C IS NOT NULL";
ResultSet rs = conn.createStatement().executeQuery(selectSql);
// Verify that the index table is used
assertPlan((PhoenixResultSet) rs, "", indexTableName);
assertTrue(rs.next());
assertEquals(70, rs.getInt(1));
assertEquals("a", rs.getString(2));
assertFalse(rs.next());
//explain plan verify to check if partial index is used
rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql);
assertTrue(rs.next());
assertTrue(rs.getString(1).contains(indexTableName));
// Add more rows to test the index write path
conn.createStatement().execute("upsert into " + dataTableName +
" values ('id3', 20, 2, 3.14, 'a')");
conn.commit();
conn.createStatement().execute("upsert into " + dataTableName +
" values ('id4', 90, 2, 3.14, 'a')");
conn.commit();
conn.createStatement().execute("upsert into " + dataTableName +
" (id, A, D) values ('id5', 150, 'b')");
conn.commit();
// Verify that index table includes only the rows where B is not null
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals(70, rs.getInt(1));
assertEquals("a", rs.getString(2));
assertTrue(rs.next());
assertEquals(90, rs.getInt(1));
assertEquals("a", rs.getString(2));
assertFalse(rs.next());
rs = conn.createStatement().executeQuery("SELECT Count(*) from " + dataTableName);
// Verify that the index table is not used
assertPlan((PhoenixResultSet) rs, "", dataTableName);
assertTrue(rs.next());
assertEquals(5, rs.getInt(1));
//explain plan verify to check if partial index is not used
rs = conn.createStatement().executeQuery("EXPLAIN " + "SELECT Count(*) from " + dataTableName);
assertTrue(rs.next());
assertFalse(rs.getString(1).contains(indexTableName));
// Overwrite an existing row that satisfies the index WHERE clause such that
// the new version of the row does not satisfy the index where clause anymore. This
// should result in deleting the index row.
conn.createStatement().execute("upsert into " + dataTableName +
" (ID, B) values ('id4', null)");
conn.commit();
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals(70, rs.getInt(1));
assertEquals("a", rs.getString(2));
assertFalse(rs.next());
// Test index verification and repair by IndexTool
verifyIndex(dataTableName, indexTableName);
try (PhoenixConnection newConn = (PhoenixConnection) DriverManager.getConnection(getUrl())) {
PTable indexTable = newConn.getTableNoCache(indexTableName);
assertTrue(indexTable.getIndexWhere().equals("(B IS NOT NULL AND C IS NOT NULL )"));
}
}
}
@Test
public void testLike() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl())) {
String dataTableName = generateUniqueName();
conn.createStatement().execute("create table " + dataTableName +
" (id varchar not null primary key, " +
"A integer, B integer, C double, D varchar)" +
(salted ? " SALT_BUCKETS=4" : ""));
String indexTableName = generateUniqueName();
// Add rows to the data table before creating a partial index to test that the index
// will be built correctly by IndexTool
conn.createStatement().execute("upsert into " + dataTableName +
" values ('id1', 70, 2, 3.14, 'abcdef')");
conn.commit();
conn.createStatement().execute("upsert into " + dataTableName +
" (id, A, D) values ('id2', 100, 'bcdez')");
conn.commit();
conn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") +
(local ? "LOCAL " : " ") +"INDEX "
+ indexTableName + " on " + dataTableName + " (A) " +
(uncovered ? "" : "INCLUDE (B, C, D)") + " WHERE D like '%cde_' ASYNC");
IndexToolIT.runIndexTool(false, null, dataTableName, indexTableName);
String selectSql = "SELECT D from " + dataTableName +
" WHERE B is not NULL AND D like '%cde_'";
ResultSet rs = conn.createStatement().executeQuery(selectSql);
// Verify that the index table is used
assertPlan((PhoenixResultSet) rs, "", indexTableName);
assertTrue(rs.next());
assertEquals("abcdef", rs.getString(1));
assertFalse(rs.next());
//explain plan verify to check if partial index is used
rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql);
assertTrue(rs.next());
assertTrue(rs.getString(1).contains(indexTableName));
// Add more rows to test the index write path
conn.createStatement().execute("upsert into " + dataTableName +
" values ('id3', 20, 2, 3.14, 'abcdegg')");
conn.commit();
conn.createStatement().execute("upsert into " + dataTableName +
" values ('id4', 10, 2, 3.14, 'aabecdeh')");
conn.commit();
conn.createStatement().execute("upsert into " + dataTableName +
" (id, A, D) values ('id5', 150, 'bbbb')");
conn.commit();
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("aabecdeh", rs.getString(1));
assertTrue(rs.next());
assertEquals("abcdef", rs.getString(1));
assertFalse(rs.next());
selectSql = "SELECT Count(*) from " + dataTableName;
rs = conn.createStatement().executeQuery(selectSql);
// Verify that the index table is not used
assertPlan((PhoenixResultSet) rs, "", dataTableName);
assertTrue(rs.next());
assertEquals(5, rs.getInt(1));
//explain plan verify to check if partial index is not used
rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql);
assertTrue(rs.next());
assertFalse(rs.getString(1).contains(indexTableName));
// Overwrite an existing row that satisfies the index WHERE clause such that
// the new version of the row does not satisfy the index where clause anymore. This
// should result in deleting the index row.
conn.createStatement().execute("upsert into " + dataTableName +
" (id, D) values ('id4', 'zzz')");
conn.commit();
selectSql = "SELECT D from " + dataTableName +
" WHERE B is not NULL AND D like '%cde_'";
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("abcdef", rs.getString(1));
assertFalse(rs.next());
// Test index verification and repair by IndexTool
verifyIndex(dataTableName, indexTableName);
try (PhoenixConnection newConn = (PhoenixConnection) DriverManager.getConnection(getUrl())) {
PTable indexTable = newConn.getTableNoCache(indexTableName);
assertTrue(indexTable.getIndexWhere().equals("D LIKE '%cde_'"));
}
}
}
@Test
public void testPhoenixRowTimestamp() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl())) {
String dataTableName = generateUniqueName();
conn.createStatement().execute(
"create table " + dataTableName + " (id varchar not null primary key, "
+ "A integer, B integer)" + (salted ?
" SALT_BUCKETS=4" :
""));
String indexTableName = generateUniqueName();
// Add rows to the data table before creating a partial index to test that the index
// will be built correctly by IndexTool
conn.createStatement().execute(
"upsert into " + dataTableName + " values ('id1', 70, 2)");
conn.commit();
conn.createStatement().execute(
"upsert into " + dataTableName + " values ('id5', 0, 2)");
conn.commit();
Thread.sleep(10);
Timestamp before = new Timestamp(EnvironmentEdgeManager.currentTimeMillis());
String timeZoneID = Calendar.getInstance().getTimeZone().getID();
conn.createStatement().execute(
"CREATE " + (uncovered ? "UNCOVERED " : " ") + (local ? "LOCAL " : " ") +
"INDEX " + indexTableName + " on " + dataTableName + " (A) " + (
uncovered ?
"" :
"INCLUDE (B)") + " WHERE PHOENIX_ROW_TIMESTAMP() < " +
"TO_DATE('" + before + "', 'yyyy-MM-dd HH:mm:ss.SSS', '" + timeZoneID + "') ASYNC");
IndexToolIT.runIndexTool(false, null, dataTableName, indexTableName);
String selectSql = "SELECT A from " + dataTableName +
" WHERE PHOENIX_ROW_TIMESTAMP() < TO_DATE('" + before +
"', 'yyyy-MM-dd HH:mm:ss.SSS', '" + timeZoneID + "')";
ResultSet rs = conn.createStatement().executeQuery(selectSql);
// Verify that the index table is used
assertPlan((PhoenixResultSet) rs, "", indexTableName);
assertTrue(rs.next());
assertEquals(0, rs.getInt(1));
assertTrue(rs.next());
assertEquals(70, rs.getInt(1));
assertFalse(rs.next());
//explain plan verify to check if partial index is used
rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql);
assertTrue(rs.next());
assertTrue(rs.getString(1).contains(indexTableName));
// Add more rows to test the index write path
conn.createStatement().execute(
"upsert into " + dataTableName + " values ('id2', 20, 3)");
conn.commit();
conn.createStatement().execute(
"upsert into " + dataTableName + " values ('id3', 10, 4)");
conn.commit();
rs = conn.createStatement().executeQuery("SELECT Count(*) from " + dataTableName);
// Verify that the index table is not used
assertPlan((PhoenixResultSet) rs, "", dataTableName);
assertTrue(rs.next());
assertEquals(4, rs.getInt(1));
//explain plan verify to check if partial index is not used
rs = conn.createStatement().executeQuery("EXPLAIN " + "SELECT Count(*) from " + dataTableName);
assertTrue(rs.next());
assertFalse(rs.getString(1).contains(indexTableName));
rs = conn.createStatement().executeQuery("SELECT Count(*) from " + indexTableName);
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
// Overwrite an existing row that satisfies the index WHERE clause such that
// the new version of the row does not satisfy the index where clause anymore. This
// should result in deleting the index row.
conn.createStatement()
.execute("upsert into " + dataTableName + " values ('id1', 70, 2)");
conn.commit();
rs = conn.createStatement().executeQuery(selectSql);
// Verify that the index table is used
assertPlan((PhoenixResultSet) rs, "", indexTableName);
assertTrue(rs.next());
assertEquals(0, rs.getInt(1));
assertFalse(rs.next());
//explain plan verify to check if partial index is used
rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql);
assertTrue(rs.next());
assertTrue(rs.getString(1).contains(indexTableName));
// Test index verification and repair by IndexTool
verifyIndex(dataTableName, indexTableName);
}
}
@Test
public void testViewIndexes() throws Exception {
String baseTableName = generateUniqueName();
String globalViewName = generateUniqueName();
String globalViewIndexName = generateUniqueName();
String tenantViewName = generateUniqueName();
try (Connection conn = DriverManager.getConnection(getUrl())) {
conn.createStatement().execute("CREATE TABLE " + baseTableName +
" (TENANT_ID CHAR(15) NOT NULL, KP CHAR(3) NOT NULL, PK2 DATE NOT NULL, "+
"PK3 INTEGER NOT NULL, KV1 VARCHAR, KV2 VARCHAR, KV3 CHAR(15) " +
"CONSTRAINT PK PRIMARY KEY(TENANT_ID, KP, PK2, PK3)) MULTI_TENANT=true" +
(salted ? ", SALT_BUCKETS=4" : ""));
conn.createStatement().execute("CREATE VIEW " + globalViewName +
" AS SELECT * FROM " + baseTableName + " WHERE KP = '001'");
conn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") +
(local ? "LOCAL " : " ") + "INDEX " + globalViewIndexName + " on " +
globalViewName + " (PK3 DESC, KV3) " +
(uncovered ? "" : "INCLUDE (KV1)") + " WHERE KV3 IS NOT NULL ASYNC");
String tenantId = "tenantId";
Properties tenantProps = new Properties();
tenantProps.setProperty(PhoenixRuntime.TENANT_ID_ATTRIB, tenantId);
// Create a tenant specific view and view index
try (Connection tenantConn = DriverManager.getConnection(getUrl(), tenantProps)) {
tenantConn.createStatement().execute("CREATE VIEW " + tenantViewName + " AS SELECT * FROM " + globalViewName);
String tenantViewIndexName = generateUniqueName();
tenantConn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") +
(local ? "LOCAL " : " ") + "INDEX " + tenantViewIndexName + " on " +
tenantViewName + " (PK3) " +
(uncovered ? "" : "INCLUDE (KV1)") + " WHERE PK3 > 4");
PreparedStatement
stmt = tenantConn.prepareStatement("UPSERT INTO " + tenantViewName + " (PK2, PK3, KV1, KV3) VALUES (?, ?, ?, ?)");
stmt.setDate(1, new Date(100));
stmt.setInt(2, 1);
stmt.setString(3, "KV1");
stmt.setString(4, "KV3");
stmt.executeUpdate();
stmt.setDate(1, new Date(100));
stmt.setInt(2, 2);
stmt.setString(3, "KV4");
stmt.setString(4, "KV5");
stmt.executeUpdate();
stmt.setDate(1, new Date(100));
stmt.setInt(2, 3);
stmt.setString(3, "KV6");
stmt.setString(4, "KV7");
stmt.executeUpdate();
stmt.setDate(1, new Date(100));
stmt.setInt(2, 4);
stmt.setString(3, "KV8");
stmt.setString(4, "KV9");
stmt.executeUpdate();
stmt.setDate(1, new Date(100));
stmt.setInt(2, 5);
stmt.setString(3, "KV10");
stmt.setString(4, "KV11");
stmt.executeUpdate();
tenantConn.commit();
// Verify that query uses the tenant view index
ResultSet rs = tenantConn.createStatement().executeQuery("SELECT KV1 FROM " +
tenantViewName + " WHERE PK3 = 5");
assertPlan((PhoenixResultSet) rs, "", tenantViewIndexName);
assertTrue(rs.next());
assertEquals("KV10", rs.getString(1));
assertFalse(rs.next());
// Verify that query does not use the tenant view index when the partial index
// where clause does not contain the query where clause
rs = tenantConn.createStatement().executeQuery("SELECT KV1 FROM " +
tenantViewName + " WHERE PK3 = 4");
assertPlan((PhoenixResultSet) rs, "", tenantViewName);
assertTrue(rs.next());
assertEquals("KV8", rs.getString(1));
assertFalse(rs.next());
// Verify that the tenant view index has only one row
rs = tenantConn.createStatement().executeQuery("SELECT Count(*) FROM " +
tenantViewIndexName);
assertPlan((PhoenixResultSet) rs, "", tenantViewIndexName);
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
}
// Run the IndexTool MR job
IndexToolIT.runIndexTool(false, "", globalViewName,
globalViewIndexName);
try (Connection tenantConn = DriverManager.getConnection(getUrl(), tenantProps)) {
// Verify that the query uses the global view index
ResultSet rs = tenantConn.createStatement().executeQuery("SELECT KV1 FROM " +
tenantViewName + " WHERE PK3 = 1 AND KV3 = 'KV3'");
assertPlan((PhoenixResultSet) rs, "", tenantViewName +
"#" + globalViewIndexName);
assertTrue(rs.next());
assertEquals("KV1", rs.getString(1));
assertFalse(rs.next());
}
// Verify that the query uses the global view index
ResultSet rs = conn.createStatement().executeQuery("SELECT KV1 FROM " +
globalViewName + " WHERE PK3 = 1 AND KV3 = 'KV3'");
assertPlan((PhoenixResultSet) rs, "", globalViewIndexName);
assertTrue(rs.next());
assertEquals("KV1", rs.getString(1));
assertFalse(rs.next());
// Verify that the global view index has five rows
rs = conn.createStatement().executeQuery("SELECT Count(*) FROM " +
globalViewIndexName);
assertPlan((PhoenixResultSet) rs, "", globalViewIndexName);
assertTrue(rs.next());
assertEquals(5, rs.getInt(1));
}
}
@Test
public void testPartialIndexPreferredOverFullIndex() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl())) {
String dataTableName = generateUniqueName();
conn.createStatement().execute("create table " + dataTableName +
" (id varchar not null primary key, " +
"A integer, B integer, C double, D varchar)" +
(salted ? " SALT_BUCKETS=4" : ""));
conn.createStatement().execute("upsert into " + dataTableName +
" values ('id1', 10, 2, 3.14, 'a')");
conn.commit();
conn.createStatement().execute("upsert into " + dataTableName +
" (id, A, D) values ('id2', 100, 'b')");
conn.commit();
String fullIndexTableName = generateUniqueName();
conn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") +
(local ? "LOCAL " : " ") +"INDEX "
+ fullIndexTableName + " on " + dataTableName + " (A) " +
(uncovered ? "" : "INCLUDE (B, C, D)") + " ASYNC");
IndexToolIT.runIndexTool(false, null, dataTableName, fullIndexTableName);
String partialIndexTableName = generateUniqueName();
conn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") +
(local ? "LOCAL " : " ") +"INDEX "
+ partialIndexTableName + " on " + dataTableName + " (A) " +
(uncovered ? "" : "INCLUDE (B, C, D)") + " WHERE A > 50 ASYNC");
IndexToolIT.runIndexTool(false, null, dataTableName, partialIndexTableName);
String selectSql = "SELECT D from " + dataTableName + " WHERE A > 60";
// Verify that the partial index table is used
ResultSet rs = conn.createStatement().executeQuery(selectSql);
assertPlan((PhoenixResultSet) rs, "", partialIndexTableName);
assertTrue(rs.next());
assertEquals("b", rs.getString(1));
assertFalse(rs.next());
//explain plan verify to check if partial index is used
rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql);
assertTrue(rs.next());
assertTrue(rs.getString(1).contains(partialIndexTableName));
selectSql = "SELECT D from " + dataTableName + " WHERE A < 50";
// Verify that the full index table is used
rs = conn.createStatement().executeQuery(selectSql);
assertPlan((PhoenixResultSet) rs, "", fullIndexTableName);
assertTrue(rs.next());
assertEquals("a", rs.getString(1));
assertFalse(rs.next());
//explain plan verify to check if full index is used
rs = conn.createStatement().executeQuery("EXPLAIN " + selectSql);
assertTrue(rs.next());
assertTrue(rs.getString(1).contains(fullIndexTableName));
}
}
@Test
public void testPartialIndexWithJson() throws Exception {
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
conn.setAutoCommit(true);
String dataTableName = generateUniqueName();
conn.createStatement().execute("create table " + dataTableName +
" (id varchar not null primary key, " +
"A integer, B integer, C double, D varchar, jsoncol json)");
String indexTableName = generateUniqueName();
String json = "{\"info\":{\"age\": %s }}";
// Add rows to the data table before creating a partial index to test that the index
// will be built correctly by IndexTool
conn.createStatement().execute(
"upsert into " + dataTableName + " values ('id1', 25, 2, 3.14, 'a','" +
String.format(json, 25) + "')");
conn.createStatement().execute(
"upsert into " + dataTableName + " (id, A, D, jsoncol)" +
" values ('id2', 100, 'b','" + String.format(json, 100) + "')");
conn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") +
(local ? "LOCAL " : " ") + "INDEX " + indexTableName +
" on " + dataTableName + " (CAST(TO_NUMBER(JSON_VALUE(jsoncol, '$.info.age')) AS INTEGER)) " +
(uncovered ? "" : "INCLUDE (B, C, D)") + " WHERE (CAST(TO_NUMBER(JSON_VALUE(jsoncol, '$.info.age')) AS INTEGER)) > 50 ASYNC");
IndexToolIT.runIndexTool(false, null, dataTableName, indexTableName);
String selectSql =
"SELECT D from " + dataTableName + " WHERE (CAST(TO_NUMBER(JSON_VALUE(jsoncol, '$.info.age')) AS INTEGER)) > 60";
ResultSet rs = conn.createStatement().executeQuery(selectSql);
// Verify that the index table is used
assertPlan((PhoenixResultSet) rs, "", indexTableName);
assertTrue(rs.next());
assertEquals("b", rs.getString(1));
assertFalse(rs.next());
selectSql =
"SELECT D from " + dataTableName + " WHERE (CAST(TO_NUMBER(JSON_VALUE(jsoncol, '$.info.age')) AS INTEGER)) = 50";
rs = conn.createStatement().executeQuery(selectSql);
// Verify that the index table is not used
assertPlan((PhoenixResultSet) rs, "", dataTableName);
// Add more rows to test the index write path
conn.createStatement().execute(
"upsert into " + dataTableName + " values ('id3', 50, 2, 9.5, 'c','" + String.format(
json, 50) + "')");
conn.createStatement().execute(
"upsert into " + dataTableName + " values ('id4', 75, 2, 9.5, 'd','" + String.format(
json, 75) + "')");
// Verify that index table includes only the rows with A > 50
selectSql = "SELECT * from " + indexTableName;
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals(75, rs.getInt(1));
assertTrue(rs.next());
assertEquals(100, rs.getInt(1));
assertFalse(rs.next());
// Overwrite an existing row that satisfies the index WHERE clause
// such that the new version of the row does not satisfy the index where clause
// anymore. This should result in deleting the index row.
String dml =
"UPSERT INTO " + dataTableName + " values ('id2', 0, 2, 9.5, 'd','" + String.format(
json, 0) + "')";
conn.createStatement().execute(dml);
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals(75, rs.getInt(1));
assertFalse(rs.next());
// Retrieve the updated row from the data table and verify that the index table is not used
selectSql =
"SELECT ID from " + dataTableName + " WHERE (CAST(TO_NUMBER(JSON_VALUE(jsoncol, '$.info.age')) AS INTEGER)) = 0";
rs = conn.createStatement().executeQuery(selectSql);
assertPlan((PhoenixResultSet) rs, "", dataTableName);
assertTrue(rs.next());
assertEquals("id2", rs.getString(1));
// Test index verification and repair by IndexTool
verifyIndex(dataTableName, indexTableName);
try (Connection newConn = DriverManager.getConnection(getUrl())) {
PTable indexTable = PhoenixRuntime.getTableNoCache(newConn, indexTableName);
assertTrue(StringUtils.deleteWhitespace(indexTable.getIndexWhere())
.equals("CAST(TO_NUMBER(JSON_VALUE(JSONCOL,'$.info.age'))ASINTEGER)>50"));
}
}
}
@Test
public void testPartialIndexWithJsonExists() throws Exception {
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
conn.setAutoCommit(true);
String dataTableName = generateUniqueName();
conn.createStatement().execute("create table " + dataTableName +
" (id varchar not null primary key, " +
"A integer, B integer, C double, D varchar, jsoncol json)" +
(salted ? " SALT_BUCKETS=4" : ""));
String indexTableName = generateUniqueName();
String jsonWithPathExists = "{\"info\":{\"address\":{\"exists\":true}}}";
String jsonWithoutPathExists = "{\"info\":{\"age\": 25 }}";
// Add rows to the data table before creating a partial index to test that the index
// will be built correctly by IndexTool
conn.createStatement().execute(
"upsert into " + dataTableName + " values ('id1', 70, 2, 3.14, 'a','" + jsonWithPathExists + "')");
conn.createStatement().execute(
"upsert into " + dataTableName + " (id, A, D, jsoncol) values ('id2', 100, 'b','" + jsonWithoutPathExists + "')");
conn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") +
(local ? "LOCAL " : " ") + "INDEX " + indexTableName + " on " + dataTableName + " (A) " +
(uncovered ? "" : "INCLUDE (B, C, D)") + " WHERE JSON_EXISTS(JSONCOL, '$.info.address.exists') ASYNC");
IndexToolIT.runIndexTool(false, null, dataTableName, indexTableName);
String selectSql =
"SELECT " + (uncovered ? " " : "/*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ ") +
" A, D from " + dataTableName + " WHERE A > 60 AND JSON_EXISTS(jsoncol, '$.info.address.exists')";
ResultSet rs = conn.createStatement().executeQuery(selectSql);
// Verify that the index table is used
assertPlan((PhoenixResultSet) rs, "", indexTableName);
assertTrue(rs.next());
assertEquals(70, rs.getInt(1));
assertEquals("a", rs.getString(2));
assertFalse(rs.next());
// Add more rows to test the index write path
conn.createStatement().execute(
"upsert into " + dataTableName + " values ('id3', 20, 2, 3.14, 'a','" + jsonWithPathExists + "')");
conn.createStatement().execute(
"upsert into " + dataTableName + " values ('id4', 90, 2, 3.14, 'a','" + jsonWithPathExists + "')");
conn.createStatement().execute(
"upsert into " + dataTableName + " (id, A, D, jsoncol) values ('id5', 150, 'b','" + jsonWithoutPathExists + "')");
// Verify that index table includes only the rows where jsonPath Exists
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals(70, rs.getInt(1));
assertEquals("a", rs.getString(2));
assertTrue(rs.next());
assertEquals(90, rs.getInt(1));
assertEquals("a", rs.getString(2));
assertFalse(rs.next());
rs = conn.createStatement().executeQuery("SELECT Count(*) from " + dataTableName);
// Verify that the index table is not used
assertPlan((PhoenixResultSet) rs, "", dataTableName);
assertTrue(rs.next());
assertEquals(5, rs.getInt(1));
// Overwrite an existing row that satisfies the index WHERE clause such that
// the new version of the row does not satisfy the index where clause anymore. This
// should result in deleting the index row.
conn.createStatement().execute(
"upsert into " + dataTableName + " (ID, B, jsoncol) values ('id4', null, '" + jsonWithoutPathExists + "')");
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals(70, rs.getInt(1));
assertEquals("a", rs.getString(2));
assertFalse(rs.next());
// Test index verification and repair by IndexTool
verifyIndex(dataTableName, indexTableName);
try (Connection newConn = DriverManager.getConnection(getUrl())) {
PTable indexTable = PhoenixRuntime.getTableNoCache(newConn, indexTableName);
assertTrue(StringUtils.deleteWhitespace(indexTable.getIndexWhere())
.equals("JSON_EXISTS(JSONCOL,'$.info.address.exists')"));
}
}
}
@Test
public void testPartialIndexWithIndexHint() throws Exception {
try(Connection conn = DriverManager.getConnection(getUrl());
Statement stmt = conn.createStatement()) {
String dataTableName = generateUniqueName();
stmt.execute("create table " + dataTableName + " (id1 varchar not null, id2 integer not null, "
+ "A integer constraint pk primary key (id1, id2))" + (salted ? " SALT_BUCKETS=4" : ""));
stmt.execute("upsert into " + dataTableName + " values ('id11', 10, 1)");
conn.commit();
stmt.execute("upsert into " + dataTableName + " values ('id12', 100, 2)");
conn.commit();
String indexTableName = generateUniqueName();
stmt.execute("CREATE " + (uncovered ? "UNCOVERED " : " ") +
(local ? "LOCAL " : " ") +"INDEX "
+ indexTableName + " on " + dataTableName + " (id2, id1) " +
(uncovered ? "" : "INCLUDE (A)") + " WHERE id2 > 50");
// Index hint provided and query plan using partial index is usable
String selectSql = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ "
+ "A from " + dataTableName + " WHERE id2 = 100 AND id1 = 'id12'";
ResultSet rs = stmt.executeQuery("EXPLAIN " + selectSql);
String actualQueryPlan = QueryUtil.getExplainPlan(rs);
assertTrue(actualQueryPlan.contains("POINT LOOKUP ON 1 KEY OVER " + indexTableName));
rs = stmt.executeQuery(selectSql);
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
assertFalse(rs.next());
// Index hint provided but query plan using partial index is not usable so, no data
selectSql = "SELECT /*+ INDEX(" + dataTableName + " " + indexTableName + ")*/ "
+ "A from " + dataTableName + " WHERE id2 = 10 AND id1 = 'id11'";
rs = stmt.executeQuery("EXPLAIN " + selectSql);
actualQueryPlan = QueryUtil.getExplainPlan(rs);
assertTrue(actualQueryPlan.contains("POINT LOOKUP ON 1 KEY OVER " + indexTableName));
rs = stmt.executeQuery(selectSql);
assertFalse(rs.next());
// No index hint so, use data table only as its point lookup
selectSql = "SELECT A from " + dataTableName + " WHERE id2 = 10 AND id1 = 'id11'";
rs = stmt.executeQuery("EXPLAIN " + selectSql);
actualQueryPlan = QueryUtil.getExplainPlan(rs);
assertTrue(actualQueryPlan.contains("POINT LOOKUP ON 1 KEY OVER " + dataTableName));
rs = stmt.executeQuery(selectSql);
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
assertFalse(rs.next());
}
}
}