From a67a4277a40feda4cd4c20ba4d2811d69b622d78 Mon Sep 17 00:00:00 2001 From: Viraj Jasani Date: Thu, 13 Aug 2026 14:10:25 -0700 Subject: [PATCH 1/3] PHOENIX-7982 Data corruption in UPSERT SELECT with VARBINARY_ENCODED --- .../phoenix/compile/UpsertCompiler.java | 14 +- .../phoenix/schema/types/PVarbinary.java | 8 + .../schema/types/PVarbinaryEncoded.java | 5 + .../phoenix/end2end/VarBinaryEncoded1IT.java | 196 +++++++++++++++++- .../VarBinaryEncodedIndexUpsertSelectIT.java | 140 +++++++++++++ .../VarBinaryEncodedServerUpsertSelectIT.java | 118 +++++++++++ .../VarBinaryEncodedUpsertSelectTestUtil.java | 173 ++++++++++++++++ 7 files changed, 650 insertions(+), 4 deletions(-) create mode 100644 phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncodedIndexUpsertSelectIT.java create mode 100644 phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncodedServerUpsertSelectIT.java create mode 100644 phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncodedUpsertSelectTestUtil.java diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java index b9dfbc33e57..106f5071e34 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java @@ -112,6 +112,7 @@ import org.apache.phoenix.schema.types.PTimestamp; import org.apache.phoenix.schema.types.PUnsignedLong; import org.apache.phoenix.schema.types.PVarbinary; +import org.apache.phoenix.schema.types.PVarbinaryEncoded; import org.apache.phoenix.util.ByteUtil; import org.apache.phoenix.util.ExpressionUtil; import org.apache.phoenix.util.IndexUtil; @@ -271,9 +272,16 @@ public static MutationState upsertSelect(StatementContext childContext, TableRef throw new DataExceedsCapacityException(column.getDataType(), column.getMaxLength(), column.getScale(), column.getName().getString()); } - column.getDataType().coerceBytes(ptr, value, column.getDataType(), precision, scale, - SortOrder.getDefault(), column.getMaxLength(), column.getScale(), column.getSortOrder(), - table.rowKeyOrderOptimizable()); + if (column.getDataType() == PVarbinaryEncoded.INSTANCE) { + // ptr holds the decoded value, because that is what the result set hands back for + // VARBINARY_ENCODED. coerceBytes() cannot restore the encoding here: it treats all + // binary types as byte comparable and so leaves ptr untouched (PHOENIX-7982). + ptr.set(column.getDataType().toBytes(value, column.getSortOrder())); + } else { + column.getDataType().coerceBytes(ptr, value, column.getDataType(), precision, scale, + SortOrder.getDefault(), column.getMaxLength(), column.getScale(), + column.getSortOrder(), table.rowKeyOrderOptimizable()); + } values[j] = ByteUtil.copyKeyBytesIfNecessary(ptr); } setValues(values, pkSlotIndexes, columnIndexes, table, mutation, statement, diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinary.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinary.java index e13c6444e8b..3199302a54a 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinary.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinary.java @@ -88,6 +88,9 @@ public Object toObject(byte[] bytes, int offset, int length, PDataType actualTyp @Override public Object toObject(Object object, PDataType actualType) { + if (actualType == PVarbinaryEncoded.INSTANCE) { + return object; + } return actualType.toBytes(object); } @@ -112,6 +115,11 @@ public boolean isCoercibleTo(PDataType targetType) { return equalsAny(targetType, this, PBinary.INSTANCE, PVarbinaryEncoded.INSTANCE); } + @Override + public boolean isBytesComparableWith(PDataType otherType) { + return super.isBytesComparableWith(otherType) && otherType != PVarbinaryEncoded.INSTANCE; + } + @Override public int compareTo(Object lhs, Object rhs, PDataType rhsType) { if (lhs == null && rhs == null) { diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinaryEncoded.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinaryEncoded.java index 9513c9e7101..e1fb7586898 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinaryEncoded.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinaryEncoded.java @@ -185,4 +185,9 @@ public boolean isCoercibleTo(PDataType targetType) { return equalsAny(targetType, this, PBinary.INSTANCE, PVarbinary.INSTANCE); } + @Override + public boolean isBytesComparableWith(PDataType otherType) { + return this.equals(otherType); + } + } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded1IT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded1IT.java index 88f61517781..639f755a6d7 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded1IT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded1IT.java @@ -17,6 +17,13 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.MULTI_PK_COLUMNS; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.assertRows; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.assertRowsAndRowKeys; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.createMultiColumnPkTable; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.createTable; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.upsertEncodedMultiPkRow; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.upsertEncodedRow; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import java.sql.Connection; @@ -76,7 +83,8 @@ public VarBinaryEncoded1IT(boolean columnEncoded, String transactionProvider, bo } @Parameterized.Parameters( - name = "VarBinary1IT_columnEncoded={0}, transactionProvider={1}, mutable={2}") + name = "VarBinary1IT_columnEncoded={0}, transactionProvider={1}, mutable={2}," + + " isBindStatement={3}") public static synchronized Collection data() { return Arrays.asList(new Object[][] { { false, null, false, false }, { false, "OMID", false, false }, { false, null, true, false }, { false, "OMID", true, false }, @@ -2268,4 +2276,190 @@ private static void upsertRow(PreparedStatement preparedStatement, double b10, b preparedStatement.executeUpdate(); } + @Test + public void testUpsertSelectVarBinaryEncodedRowKey() throws Exception { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + final String sourceTable = generateUniqueName(); + final String targetTable = generateUniqueName(); + byte[] pk1 = new byte[] { 1, 0, 2 }; + byte[] col1 = new byte[] { 10, 20, 30 }; + byte[] pk2 = new byte[] { 0, 0, 3, 0 }; + byte[] col2 = new byte[] { 40, 50 }; + + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + createTable(conn, sourceTable, tableDDLOptions); + createTable(conn, targetTable, tableDDLOptions); + + upsertEncodedRow(conn, sourceTable, pk1, col1, isBindStatement); + upsertEncodedRow(conn, sourceTable, pk2, col2, isBindStatement); + upsertEncodedRow(conn, targetTable, pk1, col1, isBindStatement); + upsertEncodedRow(conn, targetTable, pk2, col2, isBindStatement); + conn.commit(); + + byte[][] row2 = new byte[][] { pk2, col2 }; + byte[][] row1 = new byte[][] { pk1, col1 }; + + assertRowsAndRowKeys(conn, sourceTable, row2, row1); + assertRowsAndRowKeys(conn, targetTable, row2, row1); + + conn.createStatement().execute( + "UPSERT INTO " + targetTable + " (PK1, COL1) SELECT PK1, COL1 FROM " + sourceTable); + conn.commit(); + + assertRowsAndRowKeys(conn, targetTable, row2, row1); + } + } + + @Test + public void testUpsertSelectVarBinaryEncodedColumnValue() throws Exception { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + final String sourceTable = generateUniqueName(); + final String targetTable = generateUniqueName(); + byte[] pk = new byte[] { 1, 2, 3 }; + byte[] col = new byte[] { 0, -1, 5 }; + + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + conn.setAutoCommit(true); + createTable(conn, sourceTable, tableDDLOptions); + createTable(conn, targetTable, tableDDLOptions); + + upsertEncodedRow(conn, sourceTable, pk, col, isBindStatement); + + byte[][] row = new byte[][] { pk, col }; + assertRowsAndRowKeys(conn, sourceTable, row); + conn.createStatement().execute( + "UPSERT INTO " + targetTable + " (PK1, COL1) SELECT PK1, COL1 FROM " + sourceTable); + + assertRowsAndRowKeys(conn, targetTable, row); + } + } + + @Test + public void testUpsertSelectVarBinaryIntoVarBinaryEncoded() throws Exception { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + final String sourceTable = generateUniqueName(); + final String targetTable = generateUniqueName(); + + byte[] pk = new byte[] { 1, 2, 3 }; + byte[] col = new byte[] { 0, -1, 5 }; + + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + conn.createStatement() + .execute("CREATE TABLE " + sourceTable + " (PK1 VARBINARY NOT NULL, COL1 VARBINARY" + + " CONSTRAINT pk PRIMARY KEY(PK1)) " + tableDDLOptions); + createTable(conn, targetTable, tableDDLOptions); + + try (PreparedStatement preparedStatement = + conn.prepareStatement("UPSERT INTO " + sourceTable + " (PK1, COL1) VALUES (?, ?)")) { + preparedStatement.setBytes(1, pk); + preparedStatement.setBytes(2, col); + preparedStatement.executeUpdate(); + } + conn.commit(); + + assertRowsAndRowKeys(conn, sourceTable, new byte[][] { pk, col }); + conn.createStatement().execute( + "UPSERT INTO " + targetTable + " (PK1, COL1) SELECT PK1, COL1 FROM " + sourceTable); + conn.commit(); + + assertRowsAndRowKeys(conn, targetTable, new byte[][] { pk, col }); + } + } + + @Test + public void testUpsertSelectVarBinaryEncodedIntoVarBinary() throws Exception { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + final String sourceTable = generateUniqueName(); + final String targetTable = generateUniqueName(); + byte[] pk = new byte[] { 1, 2, 3 }; + byte[] col = new byte[] { 0, -1, 5 }; + + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + createTable(conn, sourceTable, tableDDLOptions); + conn.createStatement() + .execute("CREATE TABLE " + targetTable + " (PK1 VARBINARY NOT NULL, COL1 VARBINARY" + + " CONSTRAINT pk PRIMARY KEY(PK1)) " + tableDDLOptions); + + upsertEncodedRow(conn, sourceTable, pk, col, isBindStatement); + conn.commit(); + + conn.createStatement().execute( + "UPSERT INTO " + targetTable + " (PK1, COL1) SELECT PK1, COL1 FROM " + sourceTable); + conn.commit(); + + try (ResultSet resultSet = + conn.createStatement().executeQuery("SELECT PK1, COL1 FROM " + targetTable)) { + Assert.assertTrue(resultSet.next()); + Assert.assertArrayEquals(pk, resultSet.getBytes(1)); + Assert.assertArrayEquals(col, resultSet.getBytes(2)); + Assert.assertFalse(resultSet.next()); + } + } + } + + @Test + public void testUpsertSelectVarBinaryEncodedMultiColumnPk() throws Exception { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + final String sourceTable = generateUniqueName(); + final String targetTable = generateUniqueName(); + + byte[] pk1 = new byte[] { 1, 0, 2 }; + byte[] pk2a = new byte[] { 9, 0, 1 }; + byte[] col1a = new byte[] { 0, -1, 5 }; + byte[] pk2b = new byte[] { 0, 3, 0, 0 }; + byte[] col1b = new byte[] { 7, 0 }; + + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + createMultiColumnPkTable(conn, sourceTable, tableDDLOptions); + createMultiColumnPkTable(conn, targetTable, tableDDLOptions); + + upsertEncodedMultiPkRow(conn, sourceTable, pk1, pk2a, col1a, isBindStatement); + upsertEncodedMultiPkRow(conn, sourceTable, pk1, pk2b, col1b, isBindStatement); + upsertEncodedMultiPkRow(conn, targetTable, pk1, pk2a, col1a, isBindStatement); + upsertEncodedMultiPkRow(conn, targetTable, pk1, pk2b, col1b, isBindStatement); + conn.commit(); + + byte[][] rowA = new byte[][] { pk1, pk2a, col1a }; + byte[][] rowB = new byte[][] { pk1, pk2b, col1b }; + + assertRows(conn, sourceTable, MULTI_PK_COLUMNS, rowA, rowB); + assertRows(conn, targetTable, MULTI_PK_COLUMNS, rowA, rowB); + + conn.createStatement().execute("UPSERT INTO " + targetTable + + " (PK1, PK2, COL1) SELECT PK1, PK2, COL1 FROM " + sourceTable); + conn.commit(); + + assertRows(conn, targetTable, MULTI_PK_COLUMNS, rowA, rowB); + } + } + + @Test + public void testUpsertSelectVarBinaryEncodedSameTable() throws Exception { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + final String tableName = generateUniqueName(); + + byte[] pk1 = new byte[] { 1, 0, 2 }; + byte[] col1 = new byte[] { 0, -1, 5 }; + byte[] pk2 = new byte[] { 0, 0, 3, 0 }; + byte[] col2 = new byte[] { 40, 50 }; + + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + createTable(conn, tableName, tableDDLOptions); + + upsertEncodedRow(conn, tableName, pk1, col1, isBindStatement); + upsertEncodedRow(conn, tableName, pk2, col2, isBindStatement); + conn.commit(); + + byte[][] row2 = new byte[][] { pk2, col2 }; + byte[][] row1 = new byte[][] { pk1, col1 }; + assertRowsAndRowKeys(conn, tableName, row2, row1); + + conn.setAutoCommit(true); + conn.createStatement() + .execute("UPSERT INTO " + tableName + " (PK1, COL1) SELECT PK1, COL1 FROM " + tableName); + + assertRowsAndRowKeys(conn, tableName, row2, row1); + } + } + } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncodedIndexUpsertSelectIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncodedIndexUpsertSelectIT.java new file mode 100644 index 00000000000..4962db30bd6 --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncodedIndexUpsertSelectIT.java @@ -0,0 +1,140 @@ +/* + * 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.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.SINGLE_PK_COLUMNS; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.assertRows; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.countHBaseRows; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.createTable; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.upsertEncodedRow; +import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_RETRY_PAUSE_MS; +import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS; +import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE; +import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants; +import org.apache.phoenix.query.QueryServices; +import org.apache.phoenix.util.PropertiesUtil; +import org.apache.phoenix.util.ReadOnlyProps; +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 org.apache.phoenix.thirdparty.com.google.common.collect.Maps; + +@Category(NeedsOwnMiniClusterTest.class) +@RunWith(Parameterized.class) +public class VarBinaryEncodedIndexUpsertSelectIT extends ParallelStatsDisabledIT { + + private static final long EVENTUAL_CONSISTENCY_WAIT_MS = 12000; + + private final boolean coveredIndex; + private final boolean eventual; + + public VarBinaryEncodedIndexUpsertSelectIT(boolean coveredIndex, boolean eventual) { + this.coveredIndex = coveredIndex; + this.eventual = eventual; + } + + @BeforeClass + public static synchronized void doSetup() throws Exception { + Map props = Maps.newHashMapWithExpectedSize(5); + props.put(BaseScannerRegionObserverConstants.PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY, + Integer.toString(60 * 60)); + props.put(QueryServices.USE_STATS_FOR_PARALLELIZATION, Boolean.toString(false)); + props.put(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, Integer.toString(200)); + props.put(INDEX_CDC_CONSUMER_RETRY_PAUSE_MS, Integer.toString(5)); + props.put(PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, Boolean.FALSE.toString()); + setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator())); + } + + @Parameterized.Parameters(name = "VarBinaryIndexUpsertSelectIT_coveredIndex={0}, eventual={1}") + public static synchronized Collection data() { + List params = new ArrayList<>(); + for (boolean coveredIndex : new boolean[] { false, true }) { + for (boolean eventual : new boolean[] { false, true }) { + params.add(new Object[] { coveredIndex, eventual }); + } + } + return params; + } + + private void waitForEventualConsistency() throws InterruptedException { + if (eventual) { + Thread.sleep(EVENTUAL_CONSISTENCY_WAIT_MS); + } + } + + @Test + public void testUpsertSelectVarBinaryEncodedWithIndex() throws Exception { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + final String sourceTable = generateUniqueName(); + final String targetTable = generateUniqueName(); + final String indexName = generateUniqueName(); + + byte[] pk1 = new byte[] { 1, 0, 2 }; + byte[] col1 = new byte[] { 0, -1, 5 }; + byte[] pk2 = new byte[] { 0, 0, 3, 0 }; + byte[] col2 = new byte[] { 7, 0 }; + + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + createTable(conn, sourceTable, ""); + createTable(conn, targetTable, ""); + + String consistencyClause = eventual ? " CONSISTENCY = EVENTUAL" : " CONSISTENCY = STRONG"; + if (this.coveredIndex) { + conn.createStatement().execute( + "CREATE INDEX " + indexName + " ON " + targetTable + " (COL1, COL2)" + consistencyClause); + } else { + conn.createStatement().execute("CREATE UNCOVERED INDEX " + indexName + " ON " + targetTable + + " (COL1, COL2)" + consistencyClause); + } + + upsertEncodedRow(conn, sourceTable, pk1, col1, "TEXT1", true); + upsertEncodedRow(conn, sourceTable, pk2, col2, "TEXT2", true); + upsertEncodedRow(conn, targetTable, pk1, col1, "TEXT1", true); + upsertEncodedRow(conn, targetTable, pk2, col2, "TEXT2", true); + conn.commit(); + waitForEventualConsistency(); + + byte[][] row2 = new byte[][] { pk2, col2 }; + byte[][] row1 = new byte[][] { pk1, col1 }; + Assert.assertEquals("index rows", 2, countHBaseRows(conn, indexName)); + assertRows(conn, targetTable, SINGLE_PK_COLUMNS, row2, row1); + + conn.createStatement().execute("UPSERT INTO " + targetTable + + " (PK1, COL1, COL2) SELECT PK1, COL1, COL2 FROM " + sourceTable); + conn.commit(); + waitForEventualConsistency(); + + Assert.assertEquals("index rows", 2, countHBaseRows(conn, indexName)); + assertRows(conn, targetTable, SINGLE_PK_COLUMNS, row2, row1); + } + } + +} diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncodedServerUpsertSelectIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncodedServerUpsertSelectIT.java new file mode 100644 index 00000000000..9cd931c6688 --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncodedServerUpsertSelectIT.java @@ -0,0 +1,118 @@ +/* + * 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.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.SINGLE_PK_COLUMNS; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.assertRows; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.assertRowsAndRowKeys; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.createTable; +import static org.apache.phoenix.end2end.VarBinaryEncodedUpsertSelectTestUtil.upsertEncodedRow; +import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.Map; +import java.util.Properties; +import org.apache.phoenix.query.QueryServices; +import org.apache.phoenix.util.PropertiesUtil; +import org.apache.phoenix.util.ReadOnlyProps; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import org.apache.phoenix.thirdparty.com.google.common.collect.Maps; + +@Category(NeedsOwnMiniClusterTest.class) +public class VarBinaryEncodedServerUpsertSelectIT extends ParallelStatsDisabledIT { + + @BeforeClass + public static synchronized void doSetup() throws Exception { + Map props = Maps.newHashMapWithExpectedSize(1); + props.put(QueryServices.ENABLE_SERVER_UPSERT_SELECT, Boolean.TRUE.toString()); + setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator())); + } + + @Test + public void testServerUpsertSelectVarBinaryEncoded() throws Exception { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + final String sourceTable = generateUniqueName(); + final String targetTable = generateUniqueName(); + + byte[] pk1 = new byte[] { 1, 0, 2 }; + byte[] col1 = new byte[] { 0, -1, 5 }; + byte[] pk2 = new byte[] { 0, 0, 3, 0 }; + byte[] col2 = new byte[] { 40, 50 }; + + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + conn.setAutoCommit(true); + createTable(conn, sourceTable, ""); + createTable(conn, targetTable, ""); + + upsertEncodedRow(conn, sourceTable, pk1, col1); + upsertEncodedRow(conn, sourceTable, pk2, col2); + upsertEncodedRow(conn, targetTable, pk1, col1); + upsertEncodedRow(conn, targetTable, pk2, col2); + + byte[][] row2 = new byte[][] { pk2, col2 }; + byte[][] row1 = new byte[][] { pk1, col1 }; + assertRowsAndRowKeys(conn, sourceTable, row2, row1); + assertRowsAndRowKeys(conn, targetTable, row2, row1); + + String upsertSelect = + "UPSERT INTO " + targetTable + " (PK1, COL1) SELECT PK1, COL1 FROM " + sourceTable; + conn.createStatement().execute(upsertSelect); + + assertRowsAndRowKeys(conn, targetTable, row2, row1); + } + } + + @Test + public void testServerUpsertSelectVarBinaryEncodedIntoDescendingPk() throws Exception { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + final String sourceTable = generateUniqueName(); + final String targetTable = generateUniqueName(); + + byte[] pk1 = new byte[] { 1, 0, 2 }; + byte[] col1 = new byte[] { 0, -1, 5 }; + byte[] pk2 = new byte[] { 0, 0, 3, 0 }; + byte[] col2 = new byte[] { 40, 50 }; + + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + conn.setAutoCommit(true); + createTable(conn, sourceTable, ""); + createTable(conn, targetTable, "", true); + + upsertEncodedRow(conn, sourceTable, pk1, col1); + upsertEncodedRow(conn, sourceTable, pk2, col2); + upsertEncodedRow(conn, targetTable, pk1, col1); + upsertEncodedRow(conn, targetTable, pk2, col2); + + byte[][] row1 = new byte[][] { pk1, col1 }; + byte[][] row2 = new byte[][] { pk2, col2 }; + assertRowsAndRowKeys(conn, sourceTable, row2, row1); + assertRows(conn, targetTable, SINGLE_PK_COLUMNS, row1, row2); + + String upsertSelect = + "UPSERT INTO " + targetTable + " (PK1, COL1) SELECT PK1, COL1 FROM " + sourceTable; + conn.createStatement().execute(upsertSelect); + + assertRows(conn, targetTable, SINGLE_PK_COLUMNS, row1, row2); + } + } + +} diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncodedUpsertSelectTestUtil.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncodedUpsertSelectTestUtil.java new file mode 100644 index 00000000000..a62a8b27d83 --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncodedUpsertSelectTestUtil.java @@ -0,0 +1,173 @@ +/* + * 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 java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +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.jdbc.PhoenixConnection; +import org.apache.phoenix.schema.types.PVarbinary; +import org.apache.phoenix.schema.types.PVarbinaryEncoded; +import org.junit.Assert; + +final class VarBinaryEncodedUpsertSelectTestUtil { + + static final String SINGLE_PK_COLUMNS = "PK1, COL1"; + + static final String MULTI_PK_COLUMNS = "PK1, PK2, COL1"; + + private VarBinaryEncodedUpsertSelectTestUtil() { + } + + static void createTable(Connection conn, String tableName, String tableOptions) + throws SQLException { + createTable(conn, tableName, tableOptions, false); + } + + static void createTable(Connection conn, String tableName, String tableOptions, + boolean descendingPk) throws SQLException { + conn.createStatement() + .execute("CREATE TABLE " + tableName + " (PK1 VARBINARY_ENCODED NOT NULL," + + " COL1 VARBINARY_ENCODED, COL2 VARCHAR CONSTRAINT pk PRIMARY KEY(PK1" + + (descendingPk ? " DESC" : "") + ")) " + tableOptions); + } + + static void createMultiColumnPkTable(Connection conn, String tableName, String tableOptions) + throws SQLException { + conn.createStatement() + .execute("CREATE TABLE " + tableName + " (PK1 VARBINARY_ENCODED NOT NULL," + + " PK2 VARBINARY_ENCODED NOT NULL, COL1 VARBINARY_ENCODED" + + " CONSTRAINT pk PRIMARY KEY(PK1, PK2 DESC)) " + tableOptions); + } + + static void upsertEncodedRow(Connection conn, String tableName, byte[] pk, byte[] col1) + throws SQLException { + upsertEncodedRow(conn, tableName, pk, col1, null, true); + } + + static void upsertEncodedRow(Connection conn, String tableName, byte[] pk, byte[] col1, + boolean bindParameters) throws SQLException { + upsertEncodedRow(conn, tableName, pk, col1, null, bindParameters); + } + + static void upsertEncodedRow(Connection conn, String tableName, byte[] pk, byte[] col1, + String col2, boolean bindParameters) throws SQLException { + if (!bindParameters) { + conn.createStatement() + .executeUpdate("UPSERT INTO " + tableName + " (PK1, COL1, COL2) VALUES (" + toLiteral(pk) + + ", " + toLiteral(col1) + ", " + (col2 == null ? "NULL" : "'" + col2 + "'") + ")"); + return; + } + try (PreparedStatement preparedStatement = + conn.prepareStatement("UPSERT INTO " + tableName + " (PK1, COL1, COL2) VALUES (?, ?, ?)")) { + preparedStatement.setBytes(1, pk); + preparedStatement.setBytes(2, col1); + preparedStatement.setString(3, col2); + preparedStatement.executeUpdate(); + } + } + + static void upsertEncodedMultiPkRow(Connection conn, String tableName, byte[] pk1, byte[] pk2, + byte[] col1, boolean bindParameters) throws SQLException { + if (!bindParameters) { + conn.createStatement().executeUpdate("UPSERT INTO " + tableName + " (PK1, PK2, COL1) VALUES (" + + toLiteral(pk1) + ", " + toLiteral(pk2) + ", " + toLiteral(col1) + ")"); + return; + } + try (PreparedStatement preparedStatement = + conn.prepareStatement("UPSERT INTO " + tableName + " (PK1, PK2, COL1) VALUES (?, ?, ?)")) { + preparedStatement.setBytes(1, pk1); + preparedStatement.setBytes(2, pk2); + preparedStatement.setBytes(3, col1); + preparedStatement.executeUpdate(); + } + } + + private static String toLiteral(byte[] value) { + return value == null ? "NULL" : PVarbinary.INSTANCE.toStringLiteral(value); + } + + static void assertRows(Connection conn, String tableName, String columnList, + byte[][]... expectedRows) throws SQLException { + try (ResultSet resultSet = + conn.createStatement().executeQuery("SELECT /*+ NO_INDEX */ COUNT(*) FROM " + tableName)) { + Assert.assertTrue(resultSet.next()); + Assert.assertEquals(expectedRows.length, resultSet.getInt(1)); + } + + try (ResultSet resultSet = conn.createStatement() + .executeQuery("SELECT /*+ NO_INDEX */ " + columnList + " FROM " + tableName)) { + for (byte[][] expectedRow : expectedRows) { + Assert.assertTrue(resultSet.next()); + for (int i = 0; i < expectedRow.length; i++) { + Assert.assertArrayEquals(expectedRow[i], resultSet.getBytes(i + 1)); + } + } + Assert.assertFalse(resultSet.next()); + } + } + + static void assertRowsAndRowKeys(Connection conn, String tableName, byte[][]... expectedRows) + throws Exception { + assertRows(conn, tableName, SINGLE_PK_COLUMNS, expectedRows); + + List actualRowKeys = new ArrayList<>(); + try ( + Table hTable = + conn.unwrap(PhoenixConnection.class).getQueryServices().getTable(Bytes.toBytes(tableName)); + ResultScanner scanner = hTable.getScanner(new Scan())) { + for (Result result : scanner) { + actualRowKeys.add(result.getRow()); + } + } + + byte[][] expectedRowKeys = new byte[expectedRows.length][]; + for (int i = 0; i < expectedRows.length; i++) { + expectedRowKeys[i] = PVarbinaryEncoded.INSTANCE.toBytes(expectedRows[i][0]); + } + Assert.assertEquals( + Arrays.deepToString(expectedRowKeys) + " vs " + Arrays.deepToString(actualRowKeys.toArray()), + expectedRowKeys.length, actualRowKeys.size()); + for (int i = 0; i < expectedRowKeys.length; i++) { + Assert.assertArrayEquals(expectedRowKeys[i], actualRowKeys.get(i)); + } + } + + static int countHBaseRows(Connection conn, String tableName) throws Exception { + int rowCount = 0; + try ( + Table hTable = + conn.unwrap(PhoenixConnection.class).getQueryServices().getTable(Bytes.toBytes(tableName)); + ResultScanner scanner = hTable.getScanner(new Scan())) { + while (scanner.next() != null) { + rowCount++; + } + } + return rowCount; + } + +} From 80bcc7739f5afcadbb83d33ea6df0cbbc2cac3c8 Mon Sep 17 00:00:00 2001 From: Viraj Jasani Date: Thu, 13 Aug 2026 22:01:41 -0700 Subject: [PATCH 2/3] addendum --- .../phoenix/expression/CoerceExpression.java | 36 +------ .../apache/phoenix/schema/types/PBinary.java | 3 + .../phoenix/schema/types/PDataType.java | 6 +- .../phoenix/schema/types/PVarbinary.java | 7 +- .../schema/types/PVarbinaryEncoded.java | 5 - .../phoenix/end2end/VarBinaryEncoded1IT.java | 94 +++++++++++++++++++ .../phoenix/schema/types/PDataTypeTest.java | 32 +++++++ 7 files changed, 138 insertions(+), 45 deletions(-) diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/CoerceExpression.java b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/CoerceExpression.java index e3b7b60ed87..b8ed96a204d 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/CoerceExpression.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/CoerceExpression.java @@ -27,10 +27,7 @@ import org.apache.phoenix.expression.visitor.ExpressionVisitor; import org.apache.phoenix.schema.SortOrder; import org.apache.phoenix.schema.tuple.Tuple; -import org.apache.phoenix.schema.types.PBinary; import org.apache.phoenix.schema.types.PDataType; -import org.apache.phoenix.schema.types.PVarbinary; -import org.apache.phoenix.schema.types.PVarbinaryEncoded; import org.apache.phoenix.thirdparty.com.google.common.base.Preconditions; import org.apache.phoenix.thirdparty.com.google.common.collect.ImmutableList; @@ -164,35 +161,10 @@ public boolean isStateless() { @Override public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) { - // For CoerceExpression evaluation, lhs is coerced to rhs literal expression. However, - // in case of variable length binary literal expression, literal value by default - // gets VARBINARY data type. If lhs expression is of type VARBINARY_ENCODED, we should - // encode rhs literal value to VARBINARY_ENCODED type. This makes the eventual coerce - // evaluation successful. - if ( - getChild() instanceof LiteralExpression - && (getChild().getDataType() == PVarbinary.INSTANCE - || getChild().getDataType() == PBinary.INSTANCE) - && getDataType() == PVarbinaryEncoded.INSTANCE - ) { - Expression expression; - try { - expression = LiteralExpression.newConstant(((LiteralExpression) getChild()).getValue(), - PVarbinaryEncoded.INSTANCE); - } catch (SQLException e) { - throw new RuntimeException(e); - } - if (expression.evaluate(tuple, ptr)) { - getDataType().coerceBytes(ptr, null, expression.getDataType(), expression.getMaxLength(), - null, expression.getSortOrder(), maxLength, null, getSortOrder(), rowKeyOrderOptimizable); - return true; - } - } else { - if (getChild().evaluate(tuple, ptr)) { - getDataType().coerceBytes(ptr, null, getChild().getDataType(), getChild().getMaxLength(), - null, getChild().getSortOrder(), maxLength, null, getSortOrder(), rowKeyOrderOptimizable); - return true; - } + if (getChild().evaluate(tuple, ptr)) { + getDataType().coerceBytes(ptr, null, getChild().getDataType(), getChild().getMaxLength(), + null, getChild().getSortOrder(), maxLength, null, getSortOrder(), rowKeyOrderOptimizable); + return true; } return false; } diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PBinary.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PBinary.java index 31ffae9d90b..14d36ada819 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PBinary.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PBinary.java @@ -124,6 +124,9 @@ public Object toObject(byte[] bytes, int offset, int length, PDataType actualTyp @Override public Object toObject(Object object, PDataType actualType) { + if (actualType == PVarbinaryEncoded.INSTANCE) { + return object; + } return actualType.toBytes(object); } diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PDataType.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PDataType.java index 32170b0919e..cf1bcb93fb0 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PDataType.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PDataType.java @@ -91,8 +91,10 @@ public final PDataCodec getCodec() { } public boolean isBytesComparableWith(PDataType otherType) { - return equalsAny(this, otherType, PVarbinary.INSTANCE, PBinary.INSTANCE, - PVarbinaryEncoded.INSTANCE); + if (this == PVarbinaryEncoded.INSTANCE || otherType == PVarbinaryEncoded.INSTANCE) { + return this.equals(otherType); + } + return equalsAny(this, otherType, PVarbinary.INSTANCE, PBinary.INSTANCE); } /** Returns true if {@link PDataType} can be declared as primary key otherwise false. */ diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinary.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinary.java index 3199302a54a..8453e36abe5 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinary.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinary.java @@ -115,11 +115,6 @@ public boolean isCoercibleTo(PDataType targetType) { return equalsAny(targetType, this, PBinary.INSTANCE, PVarbinaryEncoded.INSTANCE); } - @Override - public boolean isBytesComparableWith(PDataType otherType) { - return super.isBytesComparableWith(otherType) && otherType != PVarbinaryEncoded.INSTANCE; - } - @Override public int compareTo(Object lhs, Object rhs, PDataType rhsType) { if (lhs == null && rhs == null) { @@ -129,7 +124,7 @@ public int compareTo(Object lhs, Object rhs, PDataType rhsType) { } else if (rhs == null) { return 1; } - if (equalsAny(rhsType, this, PBinary.INSTANCE)) { + if (equalsAny(rhsType, this, PBinary.INSTANCE, PVarbinaryEncoded.INSTANCE)) { return Bytes.compareTo((byte[]) lhs, (byte[]) rhs); } else { byte[] rhsBytes = rhsType.toBytes(rhs); diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinaryEncoded.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinaryEncoded.java index e1fb7586898..9513c9e7101 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinaryEncoded.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarbinaryEncoded.java @@ -185,9 +185,4 @@ public boolean isCoercibleTo(PDataType targetType) { return equalsAny(targetType, this, PBinary.INSTANCE, PVarbinary.INSTANCE); } - @Override - public boolean isBytesComparableWith(PDataType otherType) { - return this.equals(otherType); - } - } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded1IT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded1IT.java index 639f755a6d7..220edee05c4 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded1IT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded1IT.java @@ -2397,6 +2397,100 @@ public void testUpsertSelectVarBinaryEncodedIntoVarBinary() throws Exception { } } + @Test + public void testUpsertSelectVarBinaryEncodedSameTableOnClient() throws Exception { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + final String tableName = generateUniqueName(); + + byte[] pk1 = new byte[] { 1, 0, 2 }; + byte[] col1 = new byte[] { 0, -1, 5 }; + byte[] pk2 = new byte[] { 0, 0, 3, 0 }; + byte[] col2 = new byte[] { 40, 50 }; + + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + conn.setAutoCommit(false); + createTable(conn, tableName, tableDDLOptions); + + upsertEncodedRow(conn, tableName, pk1, col1, isBindStatement); + upsertEncodedRow(conn, tableName, pk2, col2, isBindStatement); + conn.commit(); + + byte[][] row2 = new byte[][] { pk2, col2 }; + byte[][] row1 = new byte[][] { pk1, col1 }; + assertRowsAndRowKeys(conn, tableName, row2, row1); + + conn.createStatement() + .execute("UPSERT INTO " + tableName + " (PK1, COL1) SELECT PK1, COL1 FROM " + tableName); + conn.commit(); + + assertRowsAndRowKeys(conn, tableName, row2, row1); + } + } + + @Test + public void testUpsertSelectBinaryIntoVarBinaryEncoded() throws Exception { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + final String sourceTable = generateUniqueName(); + final String targetTable = generateUniqueName(); + + byte[] pk = new byte[] { 1, 2, 3 }; + byte[] col = new byte[] { 0, -1, 5 }; + + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + conn.createStatement() + .execute("CREATE TABLE " + sourceTable + " (PK1 BINARY(3) NOT NULL, COL1 BINARY(3)" + + " CONSTRAINT pk PRIMARY KEY(PK1)) " + tableDDLOptions); + createTable(conn, targetTable, tableDDLOptions); + + try (PreparedStatement preparedStatement = + conn.prepareStatement("UPSERT INTO " + sourceTable + " (PK1, COL1) VALUES (?, ?)")) { + preparedStatement.setBytes(1, pk); + preparedStatement.setBytes(2, col); + preparedStatement.executeUpdate(); + } + conn.commit(); + + conn.createStatement().execute( + "UPSERT INTO " + targetTable + " (PK1, COL1) SELECT PK1, COL1 FROM " + sourceTable); + conn.commit(); + + assertRowsAndRowKeys(conn, sourceTable, new byte[][] { pk, col }); + assertRowsAndRowKeys(conn, targetTable, new byte[][] { pk, col }); + } + } + + @Test + public void testUpsertSelectVarBinaryEncodedIntoBinary() throws Exception { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + final String sourceTable = generateUniqueName(); + final String targetTable = generateUniqueName(); + + byte[] pk = new byte[] { 1, 2, 3 }; + byte[] col = new byte[] { 0, -1, 5 }; + + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + createTable(conn, sourceTable, tableDDLOptions); + conn.createStatement() + .execute("CREATE TABLE " + targetTable + " (PK1 BINARY(3) NOT NULL, COL1 BINARY(3)" + + " CONSTRAINT pk PRIMARY KEY(PK1)) " + tableDDLOptions); + + upsertEncodedRow(conn, sourceTable, pk, col, isBindStatement); + conn.commit(); + + conn.createStatement().execute( + "UPSERT INTO " + targetTable + " (PK1, COL1) SELECT PK1, COL1 FROM " + sourceTable); + conn.commit(); + + try (ResultSet resultSet = + conn.createStatement().executeQuery("SELECT PK1, COL1 FROM " + targetTable)) { + Assert.assertTrue(resultSet.next()); + Assert.assertArrayEquals(pk, resultSet.getBytes(1)); + Assert.assertArrayEquals(col, resultSet.getBytes(2)); + Assert.assertFalse(resultSet.next()); + } + } + } + @Test public void testUpsertSelectVarBinaryEncodedMultiColumnPk() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); diff --git a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java index 17a3cfdf023..40009dd8fc8 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java @@ -18,6 +18,7 @@ package org.apache.phoenix.schema.types; import static org.apache.phoenix.query.QueryConstants.MILLIS_IN_DAY; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -1998,4 +1999,35 @@ public void testFromSqlTypeName() { assertEquals(PVarchar.INSTANCE, PDataType.fromSqlTypeName("varchar")); } + @Test + public void testVarbinaryEncodedIsNotInterchangeableWithPlainBinary() { + byte[] value = new byte[] { 0, -1, 5 }; + assertFalse(Arrays.equals(value, PVarbinaryEncoded.INSTANCE.toBytes(value))); + + assertFalse(PVarbinary.INSTANCE.isBytesComparableWith(PVarbinaryEncoded.INSTANCE)); + assertFalse(PBinary.INSTANCE.isBytesComparableWith(PVarbinaryEncoded.INSTANCE)); + assertFalse(PVarbinaryEncoded.INSTANCE.isBytesComparableWith(PVarbinary.INSTANCE)); + assertFalse(PVarbinaryEncoded.INSTANCE.isBytesComparableWith(PBinary.INSTANCE)); + assertTrue(PVarbinary.INSTANCE.isBytesComparableWith(PBinary.INSTANCE)); + assertTrue(PBinary.INSTANCE.isBytesComparableWith(PVarbinary.INSTANCE)); + assertTrue(PVarbinaryEncoded.INSTANCE.isBytesComparableWith(PVarbinaryEncoded.INSTANCE)); + + assertArrayEquals(value, + (byte[]) PVarbinary.INSTANCE.toObject(value, PVarbinaryEncoded.INSTANCE)); + assertArrayEquals(value, (byte[]) PBinary.INSTANCE.toObject(value, PVarbinaryEncoded.INSTANCE)); + + for (PDataType target : new PDataType[] { PVarbinary.INSTANCE, PBinary.INSTANCE }) { + ImmutableBytesWritable ptr = + new ImmutableBytesWritable(PVarbinaryEncoded.INSTANCE.toBytes(value)); + target.coerceBytes(ptr, null, PVarbinaryEncoded.INSTANCE, null, null, SortOrder.ASC, null, + null, SortOrder.ASC); + assertArrayEquals("coercing VARBINARY_ENCODED to " + target, value, ptr.copyBytes()); + } + + ImmutableBytesWritable ptr = new ImmutableBytesWritable(PVarbinary.INSTANCE.toBytes(value)); + PVarbinaryEncoded.INSTANCE.coerceBytes(ptr, null, PVarbinary.INSTANCE, null, null, + SortOrder.ASC, null, null, SortOrder.ASC); + assertArrayEquals(PVarbinaryEncoded.INSTANCE.toBytes(value), ptr.copyBytes()); + } + } From 49653c152e38137a954083cb76efcfbabfd33bf9 Mon Sep 17 00:00:00 2001 From: Viraj Jasani Date: Fri, 14 Aug 2026 09:27:34 -0700 Subject: [PATCH 3/3] test timeout 20 min to 50 min --- .../src/test/java/org/apache/phoenix/query/BaseTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java index 41208464f6f..fe438ff0f25 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java @@ -201,7 +201,7 @@ public abstract class BaseTest { public static TemporaryFolder tmpFolder = new TemporaryFolder(); @ClassRule public static Timeout CLASS_TIMEOUT = - Timeout.builder().withTimeout(20, TimeUnit.MINUTES).withLookingForStuckThread(true).build(); + Timeout.builder().withTimeout(50, TimeUnit.MINUTES).withLookingForStuckThread(true).build(); private static final int dropTableTimeout = 120; // 2 mins should be long enough. private static final ThreadFactory factory = new ThreadFactoryBuilder().setDaemon(true) .setNameFormat("DROP-TABLE-BASETEST" + "-thread-%s").build();