This is an automated email from the ASF dual-hosted git repository.
sanjeet pushed a commit to branch 5.2
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/5.2 by this push:
new ec30ea2921 PHOENIX-7659: Leverage = ANY() instead of big IN list to do
huge number of point lookups in a single query (#2254)
ec30ea2921 is described below
commit ec30ea292146f131a2ada5edc173f17403a82ff9
Author: sanjeet006py <[email protected]>
AuthorDate: Fri Aug 1 17:14:54 2025 +0530
PHOENIX-7659: Leverage = ANY() instead of big IN list to do huge number of
point lookups in a single query (#2254)
---
.../org/apache/phoenix/compile/WhereOptimizer.java | 156 +++-
.../end2end/WhereOptimizerForArrayAnyIT.java | 796 +++++++++++++++++++++
2 files changed, 951 insertions(+), 1 deletion(-)
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java
index 7da2c62c41..1ae9d4c5f0 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java
@@ -55,6 +55,8 @@ import org.apache.phoenix.expression.LiteralExpression;
import org.apache.phoenix.expression.OrExpression;
import org.apache.phoenix.expression.RowKeyColumnExpression;
import org.apache.phoenix.expression.RowValueConstructorExpression;
+import org.apache.phoenix.expression.function.ArrayAnyComparisonExpression;
+import org.apache.phoenix.expression.function.ArrayElemRefExpression;
import
org.apache.phoenix.expression.function.FunctionExpression.OrderPreserving;
import org.apache.phoenix.expression.function.ScalarFunction;
import org.apache.phoenix.expression.visitor.ExpressionVisitor;
@@ -75,10 +77,12 @@ import org.apache.phoenix.schema.types.PChar;
import org.apache.phoenix.schema.types.PDataType;
import org.apache.phoenix.schema.types.PVarbinary;
import org.apache.phoenix.schema.types.PVarchar;
+import org.apache.phoenix.schema.types.PhoenixArray;
import org.apache.phoenix.util.ByteUtil;
import org.apache.phoenix.util.ScanUtil;
import org.apache.phoenix.util.SchemaUtil;
-
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.apache.phoenix.thirdparty.com.google.common.base.Optional;
import org.apache.phoenix.thirdparty.com.google.common.collect.Iterators;
import org.apache.phoenix.thirdparty.com.google.common.collect.Lists;
@@ -97,6 +101,7 @@ import edu.umd.cs.findbugs.annotations.NonNull;
* @since 0.1
*/
public class WhereOptimizer {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(WhereOptimizer.class);
private static final List<KeyRange> EVERYTHING_RANGES =
Collections.<KeyRange> singletonList(KeyRange.EVERYTHING_RANGE);
private static final List<KeyRange> SALT_PLACEHOLDER =
@@ -1749,6 +1754,155 @@ public class WhereOptimizer {
return newKeyParts(childSlot, node, new
ArrayList<KeyRange>(ranges));
}
+ /**
+ * If {@link ArrayAnyComparisonExpression} is of the form:
+ *
+ * <pre>
+ * COL = ANY(ARR)
+ * </pre>
+ *
+ * then we can extract the scan ranges for the COL, given COL is a PK
column. This
+ * syntactical pattern can be used as a replacement for a IN
expression. So, instead of
+ * following IN expression:
+ *
+ * <pre>
+ * COL IN (VAL1, VAL2, ... VALN)
+ * </pre>
+ *
+ * we can use the following ANY expression:
+ *
+ * <pre>
+ * try (Connection conn = DriverManager.getConnection(url)) {
+ * conn.createArrayOf("CHAR", new String[] {"VAL1", "VAL2", ...
"VALN"});
+ * try (PreparedStatement stmt = conn.prepareStatement(
+ * "SELECT ... FROM TABLE WHERE COL = ANY(?)")) {
+ * stmt.setArray(1, arr);
+ * ResultSet rs = stmt.executeQuery();
+ * }
+ * }
+ * </pre>
+ *
+ * This will help in saving the query parsing time as on using IN list
query parsing time
+ * increases with the size of IN list but in case of ANY expression it
is constant. Below we
+ * account for cases where COL is on the LHS or RHS of the comparison
expression.
+ * @param node {@link ArrayAnyComparisonExpression} node for
which scan ranges are
+ * to be extracted
+ * @param keyExpressions {@link RowKeyColumnExpression} for the PK
column for which scan
+ * ranges are to be extracted
+ * @return true if the scan ranges can be extracted, false otherwise
+ */
+ private boolean
shouldExtractKeyRangesForArrayAnyExpr(ArrayAnyComparisonExpression node,
+ List<Expression> keyExpressions) {
+ // {@link ArrayAnyComparisonExpression} has two children, and the
second child is
+ // comparison expression
+ Expression childExpr = node.getChildren().get(1);
+ if (!(childExpr instanceof ComparisonExpression)) {
+ return false;
+ }
+ ComparisonExpression comparisonExpr = (ComparisonExpression)
childExpr;
+
+ // Replacing IN() with =ANY() is only valid if the comparison
operator is EQUAL
+ if (comparisonExpr.getFilterOp() != CompareOperator.EQUAL) {
+ return false;
+ }
+
+ // {@link ComparisonExpression} will have two children in this
case, we need to make
+ // sure that one of them is a {@link RowKeyColumnExpression} and
the other is a {@link
+ // ArrayElemRefExpression}. Further, the first child of {@link
ArrayElemRefExpression}
+ // must be a {@link LiteralExpression}. The first child of {@link
+ // ArrayElemRefExpression} is same as the first child of {@link
+ // ArrayAnyComparisonExpression}.
+ Expression lhs = comparisonExpr.getChildren().get(0);
+ Expression rhs = comparisonExpr.getChildren().get(1);
+ if (lhs instanceof RowKeyColumnExpression && rhs instanceof
ArrayElemRefExpression) {
+ ArrayElemRefExpression arrayElemRefExpr =
(ArrayElemRefExpression) rhs;
+ if (!(arrayElemRefExpr.getChildren().get(0) instanceof
LiteralExpression)) {
+ return false;
+ }
+ // Capture {@link RowKeyColumnExpression} for the generation
of key slots.
+ keyExpressions.add(lhs);
+
+ } else if (
+ lhs instanceof ArrayElemRefExpression && rhs instanceof
RowKeyColumnExpression
+ ) {
+ ArrayElemRefExpression arrayElemRefExpr =
(ArrayElemRefExpression) lhs;
+ if (!(arrayElemRefExpr.getChildren().get(0) instanceof
LiteralExpression)) {
+ return false;
+ }
+ // Capture {@link RowKeyColumnExpression} for the generation
of key slots.
+ keyExpressions.add(rhs);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public Iterator<Expression> visitEnter(ArrayAnyComparisonExpression
node) {
+ ArrayList<Expression> keyExpressions = new ArrayList<>();
+ if (shouldExtractKeyRangesForArrayAnyExpr(node, keyExpressions)) {
+ return keyExpressions.iterator();
+ }
+ // If the scan ranges cannot be extracted, we return an empty
iterator
+ return Collections.emptyIterator();
+ }
+
+ @Override
+ public KeySlots visitLeave(ArrayAnyComparisonExpression node,
List<KeySlots> childParts) {
+ if (childParts == null || childParts.isEmpty()) {
+ return null;
+ }
+ // Doing type casting is safe here as we won't have reached here
unless the expression
+ // tree is of the form expected by the method
shouldExtractKeyRangesForArrayAnyExpr.
+ Expression arrayExpr = node.getChildren().get(0);
+ PhoenixArray arr = (PhoenixArray) ((LiteralExpression)
arrayExpr).getValue();
+ int numElements = arr.getDimensions();
+
+ ComparisonExpression comparisonExpr = (ComparisonExpression)
node.getChildren().get(1);
+ Expression lhsExpr = comparisonExpr.getChildren().get(0);
+ Expression rhsExpr = comparisonExpr.getChildren().get(1);
+ ArrayElemRefExpression arrayElemRefExpr;
+ if (lhsExpr instanceof ArrayElemRefExpression) {
+ arrayElemRefExpr = (ArrayElemRefExpression) lhsExpr;
+ } else {
+ arrayElemRefExpr = (ArrayElemRefExpression) rhsExpr;
+ }
+
+ KeySlots childSlots = childParts.get(0);
+ KeySlot childSlot = childSlots.getSlots().get(0);
+ KeyPart childPart = childSlot.getKeyPart();
+ PColumn column = childPart.getColumn();
+
+ List<KeyRange> keyRanges = new ArrayList<>();
+ try {
+ Expression coerceExpr =
CoerceExpression.create(arrayElemRefExpr,
+ column.getDataType(), column.getSortOrder(),
column.getMaxLength());
+ for (int i = 1; i <= numElements; i++) {
+ arrayElemRefExpr.setIndex(i);
+ KeyRange keyRange =
childPart.getKeyRange(CompareOperator.EQUAL, coerceExpr);
+ if (
+ keyRange == null || keyRange == KeyRange.EMPTY_RANGE
+ || keyRange == KeyRange.IS_NULL_RANGE
+ ) {
+ // Skip null range along with empty range as null
check is done via IS NULL
+ // as
+ // per SQL standards
+ continue;
+ }
+ keyRanges.add(keyRange);
+ }
+ } catch (Exception e) {
+ LOGGER.warn(
+ "Failed to wrap ArrayElemRefExpression with
CoerceExpression for column: {} and type: {}",
+ column.getName().getString(),
column.getDataType().getSqlTypeName(), e);
+ return super.visitLeave(node, childParts);
+ }
+ if (keyRanges.isEmpty()) {
+ return super.visitLeave(node, childParts);
+ }
+ return newKeyParts(childSlot, node, keyRanges);
+ }
+
@Override
public Iterator<Expression> visitEnter(IsNullExpression node) {
return Iterators.singletonIterator(node.getChildren().get(0));
diff --git
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/WhereOptimizerForArrayAnyIT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/WhereOptimizerForArrayAnyIT.java
new file mode 100644
index 0000000000..00cc8a8b1c
--- /dev/null
+++
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/WhereOptimizerForArrayAnyIT.java
@@ -0,0 +1,796 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.sql.Array;
+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.Time;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.util.HashMap;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.phoenix.compile.ExplainPlan;
+import org.apache.phoenix.compile.ExplainPlanAttributes;
+import org.apache.phoenix.compile.QueryPlan;
+import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
+import org.apache.phoenix.jdbc.PhoenixStatement;
+import org.apache.phoenix.query.BaseTest;
+import org.apache.phoenix.util.DateUtil;
+import org.apache.phoenix.util.ReadOnlyProps;
+import org.apache.phoenix.util.TestUtil;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category(NeedsOwnMiniClusterTest.class)
+public class WhereOptimizerForArrayAnyIT extends BaseTest {
+ @BeforeClass
+ public static void setup() throws Exception {
+ setUpTestDriver(new ReadOnlyProps(new HashMap<String, String>()));
+ }
+
+ @Test
+ public void testArrayAnyComparisonForNonPkColumn() throws Exception {
+ String tableName = generateUniqueName();
+ createTableASCPkColumns(tableName);
+ insertData(tableName, 1, "x", "a");
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql = "SELECT * FROM " + tableName + " WHERE col1 = ANY(?)";
+ Array arr = conn.createArrayOf("VARCHAR", new String[] { "a", "b" });
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt(1));
+ assertEquals("x", rs.getString(2));
+ assertEquals("a", rs.getString(3));
+ }
+ assertPointLookupsAreNotGenerated(stmt);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonWithInequalityOperator() throws Exception {
+ String tableName = generateUniqueName();
+ createTableASCPkColumns(tableName);
+ insertData(tableName, 2, "x", "a");
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql = "SELECT * FROM " + tableName + " WHERE pk1 > ANY(?)";
+ Array arr = conn.createArrayOf("INTEGER", new Integer[] { 1, 2, 3 });
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(2, rs.getInt(1));
+ assertEquals("x", rs.getString(2));
+ assertEquals("a", rs.getString(3));
+ }
+ assertPointLookupsAreNotGenerated(stmt);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparsionWithBindVariable() throws Exception {
+ String tableName = generateUniqueName();
+ createTableASCPkColumns(tableName);
+ insertData(tableName, 1, "x", "a");
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql = "SELECT * FROM " + tableName + " WHERE pk1 = ANY(?)
AND pk2 = 'x'";
+ Array arr = conn.createArrayOf("INTEGER", new Integer[] { 1, 2 });
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt(1));
+ assertEquals("x", rs.getString(2));
+ assertEquals("a", rs.getString(3));
+ }
+ assertPointLookupsAreGenerated(stmt, 2);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonWithLiteralArray() throws Exception {
+ String tableName = generateUniqueName();
+ createTableASCPkColumns(tableName);
+ insertData(tableName, 1, "x", "a");
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql =
+ "SELECT * FROM " + tableName + " WHERE pk1 = 1 AND pk2 =
ANY(ARRAY['x', 'y'])";
+ try (Statement stmt = conn.createStatement()) {
+ try (ResultSet rs = stmt.executeQuery(selectSql)) {
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt(1));
+ assertEquals("x", rs.getString(2));
+ assertEquals("a", rs.getString(3));
+ }
+ assertPointLookupsAreGenerated(stmt, selectSql, 2);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonWithDoubleToFloatConversion() throws
Exception {
+ String tableName = generateUniqueName();
+ String ddl =
+ "CREATE TABLE " + tableName + " (" + "pk1 FLOAT NOT NULL, " + "pk2
VARCHAR(3) NOT NULL, "
+ + "col1 VARCHAR, " + "CONSTRAINT pk PRIMARY KEY (pk1 DESC, pk2)" + ")";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(ddl);
+ conn.commit();
+ stmt.execute("UPSERT INTO " + tableName + " VALUES (2.2, 'y', 'b')");
+ conn.commit();
+ }
+ }
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql = "SELECT * FROM " + tableName + " WHERE pk1 = ANY(?)
AND pk2 = 'y'";
+ Array arr = conn.createArrayOf("DOUBLE", new Double[] { 4.4d, 2.2d, 0d
});
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(2, rs.getInt(1));
+ assertEquals("y", rs.getString(2));
+ assertEquals("b", rs.getString(3));
+ }
+ assertPointLookupsAreGenerated(stmt, 3);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonWithLongToIntegerConversion() throws
Exception {
+ String tableName = generateUniqueName();
+ String ddl =
+ "CREATE TABLE " + tableName + " (" + "pk1 INTEGER NOT NULL, " + "pk2
VARCHAR(3) NOT NULL, "
+ + "col1 VARCHAR, " + "CONSTRAINT pk PRIMARY KEY (pk1 DESC, pk2)" + ")";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(ddl);
+ conn.commit();
+ }
+ }
+ insertData(tableName, 2, "y", "b");
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql = "SELECT * FROM " + tableName + " WHERE pk1 = ANY(?)
AND pk2 = 'y'";
+ Array arr = conn.createArrayOf("BIGINT", new Long[] { 4L, 2L, 0L });
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(2, rs.getInt(1));
+ assertEquals("y", rs.getString(2));
+ assertEquals("b", rs.getString(3));
+ }
+ assertPointLookupsAreGenerated(stmt, 3);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonWithNullInArray() throws Exception {
+ String tableName = generateUniqueName();
+ createTableASCPkColumns(tableName);
+ insertData(tableName, 1, null, "a");
+ insertData(tableName, 2, "y", "b");
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql = "SELECT * FROM " + tableName + " WHERE pk1 = 1 AND
pk2 = ANY(?)";
+ Array arr = conn.createArrayOf("VARCHAR", new String[] { "y", "z", null
});
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertFalse(rs.next());
+ }
+ // 2 point lookups are generated instead of 3 as the null is not
considered as a value for
+ // VARCHAR type column
+ assertPointLookupsAreGenerated(stmt, 2);
+ }
+
+ selectSql = "SELECT * FROM " + tableName + " WHERE pk1 = 2 AND pk2 =
ANY(?)";
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(2, rs.getInt(1));
+ assertEquals("y", rs.getString(2));
+ assertEquals("b", rs.getString(3));
+ }
+ assertPointLookupsAreGenerated(stmt, 2);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonWithDescPKAndNullInArray() throws
Exception {
+ String tableName = generateUniqueName();
+ String ddl = "CREATE TABLE " + tableName + " (" + "pk1 INTEGER NOT NULL, "
+ "pk2 VARCHAR(3), "
+ + "col1 VARCHAR, " + "CONSTRAINT pk PRIMARY KEY (pk1, pk2 DESC)" + ")";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(ddl);
+ conn.commit();
+ }
+ }
+ insertData(tableName, 1, null, "a");
+ insertData(tableName, 2, "y", "b");
+ insertData(tableName, 3, "z", null);
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql = "SELECT * FROM " + tableName + " WHERE pk1 = 1 AND
pk2 = ANY(?)";
+ Array arr = conn.createArrayOf("VARCHAR", new String[] { "y", "z", null
});
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertFalse(rs.next());
+ }
+ // 2 point lookups are generated instead of 3 as the null is not
considered as a value for
+ // VARCHAR type column
+ assertPointLookupsAreGenerated(stmt, 2);
+ }
+
+ selectSql = "SELECT * FROM " + tableName + " WHERE pk1 = 2 AND pk2 =
ANY(?)";
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(2, rs.getInt(1));
+ assertEquals("y", rs.getString(2));
+ assertEquals("b", rs.getString(3));
+ }
+ assertPointLookupsAreGenerated(stmt, 2);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonForDescCharPKWithPadding() throws
Exception {
+ String tableName = generateUniqueName();
+ String ddl = "CREATE TABLE " + tableName + " (" + "pk1 CHAR(3) NOT NULL, "
+ "pk2 VARCHAR(3), "
+ + "col1 VARCHAR, " + "CONSTRAINT pk PRIMARY KEY (pk1 DESC, pk2)" + ")";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(ddl);
+ conn.commit();
+ }
+ }
+ String upsertStmt = "UPSERT INTO " + tableName + " VALUES (?, ?, ?)";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (PreparedStatement stmt = conn.prepareStatement(upsertStmt)) {
+ stmt.setString(1, "a");
+ stmt.setString(2, "b");
+ stmt.setString(3, "c");
+ stmt.executeUpdate();
+ conn.commit();
+ }
+ }
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql = "SELECT * FROM " + tableName + " WHERE pk1 = ANY(?)
AND pk2 = 'b'";
+ Array arr = conn.createArrayOf("VARCHAR", new String[] { "a", "c", null
});
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals("a", rs.getString(1));
+ assertEquals("b", rs.getString(2));
+ assertEquals("c", rs.getString(3));
+ }
+ // 3 point lookups are generated though one of the array values is
null as CHAR type pads it
+ // and the value is a string consisting only of pad characters
+ assertPointLookupsAreGenerated(stmt, 3);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonWithDecimalArray() throws Exception {
+ String tableName = generateUniqueName();
+ String ddl = "CREATE TABLE " + tableName + " (" + "pk1 DECIMAL(10, 2) NOT
NULL, "
+ + "pk2 VARCHAR(3), " + "col1 VARCHAR, " + "CONSTRAINT pk PRIMARY KEY
(pk1, pk2)" + ")";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(ddl);
+ conn.commit();
+ }
+ }
+ String upsertStmt = "UPSERT INTO " + tableName + " VALUES (?, ?, ?)";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (PreparedStatement stmt = conn.prepareStatement(upsertStmt)) {
+ stmt.setBigDecimal(1, new BigDecimal("1.23"));
+ stmt.setString(2, "x");
+ stmt.setString(3, "a");
+ stmt.executeUpdate();
+ conn.commit();
+ }
+ }
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql = "SELECT * FROM " + tableName + " WHERE pk1 = ANY(?)
AND pk2 = 'x'";
+ Array arr = conn.createArrayOf("DECIMAL", new BigDecimal[] { new
BigDecimal("1.230"),
+ new BigDecimal("2.340"), new BigDecimal("3.450") });
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(new BigDecimal("1.23"), rs.getBigDecimal(1));
+ assertEquals("x", rs.getString(2));
+ assertEquals("a", rs.getString(3));
+ }
+ assertPointLookupsAreGenerated(stmt, 3);
+ }
+ }
+ }
+
+ @Test
+ public void
testArrayAnyComparisonWithDataTypeAndSortOrderCoercionForDecimalColumn()
+ throws Exception {
+ String tableName = generateUniqueName();
+ String ddl = "CREATE TABLE " + tableName + " (" + "pk1 DECIMAL(10, 2) NOT
NULL, "
+ + "pk2 VARCHAR(3), " + "col1 VARCHAR, " + "CONSTRAINT pk PRIMARY KEY
(pk1 DESC, pk2)" + ")";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(ddl);
+ conn.commit();
+ }
+ }
+ String upsertStmt = "UPSERT INTO " + tableName + " VALUES (?, ?, ?)";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (PreparedStatement stmt = conn.prepareStatement(upsertStmt)) {
+ stmt.setBigDecimal(1, new BigDecimal("1.23"));
+ stmt.setString(2, "x");
+ stmt.setString(3, "a");
+ stmt.executeUpdate();
+ conn.commit();
+ }
+ }
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql = "SELECT * FROM " + tableName + " WHERE pk1 = ANY(?)
AND pk2 = 'x'";
+ Array arr = conn.createArrayOf("DOUBLE", new Double[] { 1.230d, 2.340d,
3.450d });
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(new BigDecimal("1.23"), rs.getBigDecimal(1));
+ assertEquals("x", rs.getString(2));
+ assertEquals("a", rs.getString(3));
+ }
+ assertPointLookupsAreGenerated(stmt, 3);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonForDateTimeColumn() throws Exception {
+ String tableName = generateUniqueName();
+ String ddl = "CREATE TABLE " + tableName + " (" + "pk1 TIMESTAMP NOT NULL,
"
+ + "pk2 TIME NOT NULL, " + "pk3 DATE NOT NULL, " + "col1 VARCHAR, "
+ + "CONSTRAINT pk PRIMARY KEY (pk1, pk2, pk3)" + ")";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(ddl);
+ conn.commit();
+ }
+ }
+ String pk1Value = "2025-07-18 10:00:00";
+ String pk2Value = "2025-07-18 11:00:00";
+ String pk3Value = "2025-07-18 12:00:00";
+ String upsertStmt = "UPSERT INTO " + tableName + " VALUES (?, ?, ?, ?)";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (PreparedStatement stmt = conn.prepareStatement(upsertStmt)) {
+ stmt.setTimestamp(1, DateUtil.parseTimestamp(pk1Value));
+ stmt.setTime(2, DateUtil.parseTime(pk2Value));
+ stmt.setDate(3, DateUtil.parseDate(pk3Value));
+ stmt.setString(4, "a");
+ stmt.executeUpdate();
+ conn.commit();
+ }
+ }
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+
+ // Use arrays as bind variables to test the point lookup optimization.
+ String selectSql = "SELECT * FROM " + tableName + " WHERE pk1 = ANY(?) "
+ "AND pk2 = ANY(?) "
+ + "AND pk3 = ANY(?)";
+ Array timestampArr = conn.createArrayOf("TIMESTAMP",
+ new Timestamp[] { DateUtil.parseTimestamp(pk1Value),
+ DateUtil.parseTimestamp("2025-07-19 10:00:00"),
+ DateUtil.parseTimestamp("2025-07-17 10:00:00"), });
+ Array timeArr = conn.createArrayOf("TIME", new Time[] {
DateUtil.parseTime(pk2Value),
+ DateUtil.parseTime("2025-07-19 11:00:00"),
DateUtil.parseTime("2025-07-17 11:00:00"), });
+ Array dateArr = conn.createArrayOf("DATE", new Date[] {
DateUtil.parseDate(pk3Value),
+ DateUtil.parseDate("2025-07-19 12:00:00"),
DateUtil.parseDate("2025-07-17 12:00:00"), });
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, timestampArr);
+ stmt.setArray(2, timeArr);
+ stmt.setArray(3, dateArr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(DateUtil.parseTimestamp(pk1Value), rs.getTimestamp(1));
+ assertEquals(DateUtil.parseTime(pk2Value), rs.getTime(2));
+ assertEquals(DateUtil.parseDate(pk3Value), rs.getDate(3));
+ }
+ assertPointLookupsAreGenerated(stmt, 3 * 3 * 3);
+ }
+
+ // Use literal arrays to test the point lookup optimization.
+ String timestampLiteralArr = "ARRAY[" + "TO_TIMESTAMP('" + pk1Value +
"'), "
+ + "TO_TIMESTAMP('" + pk2Value + "'), " + "TO_TIMESTAMP('" + pk3Value +
"')]";
+ String timeLiteralArr = "ARRAY[" + "TO_TIME('" + pk1Value + "'), " +
"TO_TIME('" + pk2Value
+ + "'), " + "TO_TIME('" + pk3Value + "')]";
+ String dateLiteralArr = "ARRAY[" + "TO_DATE('" + pk1Value + "'), " +
"TO_DATE('" + pk2Value
+ + "'), " + "TO_DATE('" + pk3Value + "')]";
+ selectSql = "SELECT * FROM " + tableName + " WHERE pk1 = ANY(" +
timestampLiteralArr + ") "
+ + "AND pk2 = ANY(" + timeLiteralArr + ")" + "AND pk3 = ANY(" +
dateLiteralArr + ")";
+ try (Statement stmt = conn.createStatement()) {
+ try (ResultSet rs = stmt.executeQuery(selectSql)) {
+ assertTrue(rs.next());
+ assertEquals(DateUtil.parseTimestamp(pk1Value), rs.getTimestamp(1));
+ assertEquals(DateUtil.parseTime(pk2Value), rs.getTime(2));
+ assertEquals(DateUtil.parseDate(pk3Value), rs.getDate(3));
+ }
+ assertPointLookupsAreGenerated(stmt, selectSql, 3 * 3 * 3);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonWithTimestampToDateCoercion() throws
Exception {
+ String tableName = generateUniqueName();
+ String ddl =
+ "CREATE TABLE " + tableName + " (" + "pk1 TIMESTAMP NOT NULL, " + "pk2
DATE NOT NULL, "
+ + "col1 VARCHAR, " + "CONSTRAINT pk PRIMARY KEY (pk1, pk2 DESC)" + ")";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(ddl);
+ conn.commit();
+ }
+ }
+ String pk1Value = "2025-07-18 10:00:00";
+ String pk2Value = "2025-07-18 11:00:00";
+ String pk3Value = "2025-07-18 12:00:00";
+ String upsertStmt = "UPSERT INTO " + tableName + " VALUES (?, ?, ?)";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (PreparedStatement stmt = conn.prepareStatement(upsertStmt)) {
+ stmt.setTimestamp(1, DateUtil.parseTimestamp(pk1Value));
+ stmt.setDate(2, DateUtil.parseDate(pk2Value));
+ stmt.setString(3, "a");
+ stmt.executeUpdate();
+ conn.commit();
+ }
+ }
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ // Use literal arrays to test the point lookup optimization.
+ String timestampLiteralArr = "ARRAY[" + "TO_TIMESTAMP('" + pk1Value +
"'), "
+ + "TO_TIMESTAMP('" + pk2Value + "'), " + "TO_TIMESTAMP('" + pk3Value +
"')]";
+ String selectSql = "SELECT * FROM " + tableName + " WHERE pk1 = ANY(" +
timestampLiteralArr
+ + ") " + "AND pk2 = ANY(" + timestampLiteralArr + ")";
+ try (Statement stmt = conn.createStatement()) {
+ try (ResultSet rs = stmt.executeQuery(selectSql)) {
+ assertTrue(rs.next());
+ assertEquals(DateUtil.parseTimestamp(pk1Value), rs.getTimestamp(1));
+ assertEquals(DateUtil.parseDate(pk2Value), rs.getDate(2));
+ assertEquals("a", rs.getString(3));
+ }
+ assertPointLookupsAreGenerated(stmt, selectSql, 3 * 3);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonForBinaryColumn() throws Exception {
+ String tableName = generateUniqueName();
+ String ddl = "CREATE TABLE " + tableName + " (" + "pk1 BINARY(3) NOT NULL,
"
+ + "pk2 VARBINARY(3) NOT NULL, " + "col1 VARCHAR, "
+ + "CONSTRAINT pk PRIMARY KEY (pk1, pk2)" + ")";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(ddl);
+ conn.commit();
+ }
+ }
+ String pk1Value = "a";
+ String pk2Value = "b";
+ String col1Value = "d";
+ String upsertStmt = "UPSERT INTO " + tableName + " VALUES (?, ?, ?)";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (PreparedStatement stmt = conn.prepareStatement(upsertStmt)) {
+ stmt.setBytes(1, pk1Value.getBytes());
+ stmt.setBytes(2, pk2Value.getBytes());
+ stmt.setString(3, col1Value);
+ stmt.executeUpdate();
+ conn.commit();
+ }
+ }
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ TestUtil.dumpTable(conn, TableName.valueOf(tableName));
+ String selectSql =
+ "SELECT * FROM " + tableName + " WHERE pk1 = ANY(?) AND pk2 = ANY(?)";
+ byte[][] nativeByteArr =
+ new byte[][] { pk1Value.getBytes(), pk2Value.getBytes() };
+ Array binaryArr = conn.createArrayOf("BINARY", nativeByteArr);
+ Array varbinaryArr = conn.createArrayOf("VARBINARY", nativeByteArr);
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, binaryArr);
+ stmt.setArray(2, varbinaryArr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertBinaryValue(pk1Value.getBytes(), rs.getBytes(1));
+ assertBinaryValue(pk2Value.getBytes(), rs.getBytes(2));
+ assertEquals(col1Value, rs.getString(3));
+ }
+ assertPointLookupsAreGenerated(stmt, 2 * 2);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonForBinaryColumnWithCoercion() throws
Exception {
+ String tableName = generateUniqueName();
+ String ddl = "CREATE TABLE " + tableName + " (" + "pk1 BINARY(3) NOT NULL,
"
+ + "pk2 VARBINARY(3) NOT NULL, " + "col1 VARCHAR, "
+ + "CONSTRAINT pk PRIMARY KEY (pk1 DESC, pk2)" + ")";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(ddl);
+ conn.commit();
+ }
+ }
+ String pk1Value = "a";
+ String pk2Value = "b";
+ String col1Value = "d";
+ String upsertStmt = "UPSERT INTO " + tableName + " VALUES (?, ?, ?)";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (PreparedStatement stmt = conn.prepareStatement(upsertStmt)) {
+ stmt.setBytes(1, pk1Value.getBytes());
+ stmt.setBytes(2, pk2Value.getBytes());
+ stmt.setString(3, col1Value);
+ stmt.executeUpdate();
+ conn.commit();
+ }
+ }
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ TestUtil.dumpTable(conn, TableName.valueOf(tableName));
+ String selectSql =
+ "SELECT * FROM " + tableName + " WHERE pk1 = ANY(?) AND pk2 = ANY(?)";
+ byte[][] nativeByteArr =
+ new byte[][] { pk1Value.getBytes(), pk2Value.getBytes() };
+ Array binaryArr = conn.createArrayOf("BINARY", nativeByteArr);
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, binaryArr);
+ stmt.setArray(2, binaryArr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertBinaryValue(pk1Value.getBytes(), rs.getBytes(1));
+ assertBinaryValue(pk2Value.getBytes(), rs.getBytes(2));
+ assertEquals(col1Value, rs.getString(3));
+ }
+ assertPointLookupsAreGenerated(stmt, 2 * 2);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonInGroupedAggregateQuery() throws Exception
{
+ String tableName = generateUniqueName();
+ String ddl = "CREATE TABLE " + tableName + " (" + "pk1 INTEGER NOT NULL, "
+ + "pk2 VARCHAR NOT NULL, " + "col1 VARCHAR, " + "CONSTRAINT pk PRIMARY
KEY (pk1, pk2)" + ")";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(ddl);
+ conn.commit();
+ }
+ }
+ insertData(tableName, 1, "a11", "b11");
+ insertData(tableName, 1, "a12", "b12");
+ insertData(tableName, 2, "a21", "b21");
+ insertData(tableName, 2, "a22", "b22");
+ insertData(tableName, 2, "a23", "b23");
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql =
+ "SELECT pk1, COUNT(*) FROM " + tableName + " WHERE pk1 = 2 AND pk2 =
ANY(?) GROUP BY pk1";
+ Array arr = conn.createArrayOf("VARCHAR", new String[] { "a11", "a21",
"a23" });
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(2, rs.getInt(1));
+ assertEquals(2, rs.getInt(2));
+ }
+ assertPointLookupsAreGenerated(stmt, 3);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonWithIndexPKColumn() throws Exception {
+ String tableName = generateUniqueName();
+ String createTableDdl = "CREATE TABLE " + tableName + " (" + "pk1 INTEGER
NOT NULL, "
+ + "pk2 VARCHAR(3), " + "col1 VARCHAR, " + "CONSTRAINT pk PRIMARY KEY
(pk1, pk2)" + ")";
+ String createIndexDdl = "CREATE INDEX idx_pk1 ON " + tableName + " (col1)";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(createTableDdl);
+ conn.commit();
+ stmt.execute(createIndexDdl);
+ conn.commit();
+ }
+ }
+ insertData(tableName, 1, "a", "b");
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql = "SELECT * FROM " + tableName + " WHERE col1 = ANY(?)";
+ Array arr = conn.createArrayOf("VARCHAR", new String[] { "a", "b", "c"
});
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt(1));
+ assertEquals("a", rs.getString(2));
+ assertEquals("b", rs.getString(3));
+ }
+ assertSkipScanIsGenerated(stmt, 3);
+ }
+ }
+ }
+
+ @Test
+ public void testArrayAnyComparisonWithRowKeyPrefix() throws Exception {
+ String tableName = generateUniqueName();
+ String ddl = "CREATE TABLE " + tableName + " (" + "pk1 INTEGER NOT NULL, "
+ "pk2 VARCHAR(3), "
+ + "col1 VARCHAR, " + "CONSTRAINT pk PRIMARY KEY (pk1, pk2)" + ")";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(ddl);
+ conn.commit();
+ }
+ }
+ insertData(tableName, 1, "a", "b");
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql = "SELECT * FROM " + tableName + " WHERE pk1 = ANY(?)";
+ Array arr = conn.createArrayOf("INTEGER", new Integer[] { 1, 2, 3 });
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt(1));
+ assertEquals("a", rs.getString(2));
+ assertEquals("b", rs.getString(3));
+ }
+ assertSkipScanIsGenerated(stmt, 3);
+ }
+ }
+ }
+
+ @Test
+ public void
testArrayAnyComparisonWhenRowKeyColumnExpressionIsNotTopLevelExpression()
+ throws Exception {
+ String tableName = generateUniqueName();
+ String ddl = "CREATE TABLE " + tableName + " (" + "pk1 INTEGER NOT NULL, "
+ "pk2 VARCHAR(3), "
+ + "col1 VARCHAR, " + "CONSTRAINT pk PRIMARY KEY (pk1, pk2)" + ")";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(ddl);
+ conn.commit();
+ }
+ }
+ insertData(tableName, 1, "a", "b");
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ String selectSql =
+ "SELECT * FROM " + tableName + " WHERE CAST (pk1 as BIGINT) = ANY(?)
AND pk2 = 'a'";
+ Array arr = conn.createArrayOf("BIGINT", new Long[] { 1L, 2L, 3L });
+ try (PreparedStatement stmt = conn.prepareStatement(selectSql)) {
+ stmt.setArray(1, arr);
+ try (ResultSet rs = stmt.executeQuery()) {
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt(1));
+ assertEquals("a", rs.getString(2));
+ assertEquals("b", rs.getString(3));
+ }
+ assertPointLookupsAreNotGenerated(stmt);
+ }
+ }
+ }
+
+ private void assertBinaryValue(byte[] expected, byte[] actual) {
+ int expectedLength = expected.length;
+ for (int i = 0; i < expectedLength; i++) {
+ assertEquals(expected[i], actual[i]);
+ }
+ }
+
+ private void assertPointLookupsAreNotGenerated(PreparedStatement stmt)
throws SQLException {
+ ExplainPlan explain =
+
stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan();
+ ExplainPlanAttributes planAttributes = explain.getPlanStepsAsAttributes();
+ assertEquals("FULL SCAN ", planAttributes.getExplainScanType());
+ }
+
+ private void assertPointLookupsAreGenerated(PreparedStatement stmt, int
noOfPointLookups)
+ throws SQLException {
+ QueryPlan queryPlan =
stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery();
+ assertPointLookupsAreGenerated(queryPlan, noOfPointLookups);
+ }
+
+ private void assertPointLookupsAreGenerated(Statement stmt, String selectSql,
+ int noOfPointLookups) throws SQLException {
+ QueryPlan queryPlan =
stmt.unwrap(PhoenixStatement.class).optimizeQuery(selectSql);
+ assertPointLookupsAreGenerated(queryPlan, noOfPointLookups);
+ }
+
+ private void assertSkipScanIsGenerated(PreparedStatement stmt, int
skipListSize)
+ throws SQLException {
+ QueryPlan queryPlan =
stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery();
+ ExplainPlan explain = queryPlan.getExplainPlan();
+ ExplainPlanAttributes planAttributes = explain.getPlanStepsAsAttributes();
+ String expectedScanType =
+ "SKIP SCAN ON " + skipListSize + " KEY" + (skipListSize > 1 ? "S " : "
");
+ assertEquals(expectedScanType, planAttributes.getExplainScanType());
+ }
+
+ private void assertPointLookupsAreGenerated(QueryPlan queryPlan, int
noOfPointLookups)
+ throws SQLException {
+ ExplainPlan explain = queryPlan.getExplainPlan();
+ ExplainPlanAttributes planAttributes = explain.getPlanStepsAsAttributes();
+ String expectedScanType =
+ "POINT LOOKUP ON " + noOfPointLookups + " KEY" + (noOfPointLookups > 1 ?
"S " : " ");
+ assertEquals(expectedScanType, planAttributes.getExplainScanType());
+ }
+
+ private void createTableASCPkColumns(String tableName) throws SQLException {
+ String ddl = "CREATE TABLE " + tableName + " (" + "pk1 INTEGER NOT NULL, "
+ "pk2 VARCHAR(3), "
+ + "col1 VARCHAR, " + "CONSTRAINT pk PRIMARY KEY (pk1, pk2)" + ")";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (Statement stmt = conn.createStatement()) {
+ stmt.execute(ddl);
+ conn.commit();
+ }
+ }
+ }
+
+ private void insertData(String tableName, int pk1, String pk2, String col1)
throws SQLException {
+ String ddl = "UPSERT INTO " + tableName + " VALUES (?, ?, ?)";
+ try (Connection conn = DriverManager.getConnection(getUrl())) {
+ try (PreparedStatement stmt = conn.prepareStatement(ddl)) {
+ stmt.setInt(1, pk1);
+ if (pk2 != null) {
+ stmt.setString(2, pk2);
+ } else {
+ stmt.setNull(2, Types.VARCHAR);
+ }
+ if (col1 != null) {
+ stmt.setString(3, col1);
+ } else {
+ stmt.setNull(3, Types.VARCHAR);
+ }
+ stmt.executeUpdate();
+ conn.commit();
+ }
+ }
+ }
+
+}