This is an automated email from the ASF dual-hosted git repository. richardantal pushed a commit to branch 5.1 in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/5.1 by this push: new c9d663c PHOENIX-5865 Column that has default value can not be correctly indexed c9d663c is described below commit c9d663ccf155a9add374b6a4f16cf1685c0fb9a9 Author: Richard Antal <antal97rich...@gmail.com> AuthorDate: Wed Dec 1 12:28:11 2021 +0100 PHOENIX-5865 Column that has default value can not be correctly indexed --- .../phoenix/end2end/IndexWithDefaultValueIT.java | 280 +++++++++++++++++++++ .../expression/KeyValueColumnExpression.java | 9 + .../expression/SingleCellColumnExpression.java | 24 +- .../function/DefaultValueExpression.java | 12 +- .../phoenix/hbase/index/AbstractValueGetter.java | 4 +- .../org/apache/phoenix/schema/MetaDataClient.java | 33 ++- .../phoenix/schema/tuple/ValueGetterTuple.java | 29 ++- 7 files changed, 362 insertions(+), 29 deletions(-) diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexWithDefaultValueIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexWithDefaultValueIT.java new file mode 100644 index 0000000..a93f4ab --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexWithDefaultValueIT.java @@ -0,0 +1,280 @@ +/* + * 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 org.junit.Test; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.Properties; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertEquals; + + +public class IndexWithDefaultValueIT extends ParallelStatsDisabledIT { + + @Test + public void testQueryTableWithIndex() throws Exception { + String tableName = generateUniqueName(); + String indexName = generateUniqueName(); + + Properties props = new Properties(); + String schema = generateUniqueName(); + Connection conn = DriverManager.getConnection(getUrl(), props); + + conn.setSchema(schema); + conn.createStatement().execute("\n" + + "create table " + tableName + "(\n" + + "pk VARCHAR,\n" + + "b VARCHAR,\n" + + "c VARCHAR default '0',\n" + + "CONSTRAINT my_pk PRIMARY KEY (pk)\n" + + ")"); + + conn.commit(); + + conn.createStatement().execute("upsert into " + tableName + " values('1','1','1')"); + conn.commit(); + + conn.createStatement().execute("CREATE INDEX " + indexName + " ON " + tableName + "(pk, b, c)"); + conn.commit(); + + + final PreparedStatement select = conn.prepareStatement( + "select * from " + tableName); + + ResultSet rs = select.executeQuery(); + + assertTrue(rs.next()); + assertEquals("1", rs.getString(3)); + assertFalse(rs.next()); + rs.close(); + conn.close(); + } + + + + @Test + public void testQueryTableWithIndexBigintDefault() throws Exception { + String tableName = generateUniqueName(); + String indexName = generateUniqueName(); + + Properties props = new Properties(); + String schema = generateUniqueName(); + Connection conn = DriverManager.getConnection(getUrl(), props); + + + conn.setSchema(schema); + conn.createStatement().execute("\n" + + "create table " + tableName + "(\n" + + "id CHAR(32) NOT NULL,\n" + + "no CHAR(32) default 'AB'," + + "total BIGINT default 0,\n" + + "score INTEGER default 0," + + "CONSTRAINT my_pk PRIMARY KEY (id)\n" + + ")"); + + conn.commit(); + + conn.createStatement().execute("upsert into " + tableName + "(id, no, total, score) values ('1111','1112', 1113, 1114)"); + conn.createStatement().execute("upsert into " + tableName + "(id, total) values ('1121', 1123)"); + conn.commit(); + + conn.createStatement().execute("CREATE INDEX " + indexName + " on " + tableName + " (no, total, score)"); + conn.commit(); + + + final PreparedStatement select = conn.prepareStatement( + "select * from " + tableName); + + ResultSet rs = select.executeQuery(); + + assertTrue(rs.next()); + assertEquals(1113L, rs.getObject(3)); + assertEquals(1114, rs.getObject(4)); + assertTrue(rs.next()); + assertEquals("AB", rs.getObject(2)); + assertEquals(1123L, rs.getObject(3)); + assertEquals(0, rs.getObject(4)); + assertFalse(rs.next()); + + rs.close(); + conn.close(); + } + + @Test + public void testQueryTableWithIndexDefaultValue() throws Exception { + String tableName = generateUniqueName(); + String indexName = generateUniqueName(); + + Properties props = new Properties(); + String schema = generateUniqueName(); + Connection conn = DriverManager.getConnection(getUrl(), props); + + + conn.setSchema(schema); + conn.createStatement().execute("\n" + + "create table " + tableName + "(\n" + + "pk1 INTEGER NOT NULL, " + + "pk2 INTEGER DEFAULT 10, " + + "CONSTRAINT my_pk PRIMARY KEY (pk1)\n" + + ")"); + + conn.commit(); + + conn.createStatement().execute("upsert into " + tableName + "(pk1, pk2) values (1,1)"); + conn.createStatement().execute("upsert into " + tableName + "(pk1, pk2) values (2, null)"); + conn.createStatement().execute("upsert into " + tableName + "(pk1) values (3)"); + conn.commit(); + + conn.createStatement().execute("CREATE INDEX " + indexName + " on " + tableName + " (pk1, pk2)"); + conn.commit(); + + + final PreparedStatement select = conn.prepareStatement( + "select * from " + tableName); + + ResultSet rs = select.executeQuery(); + + assertTrue(rs.next()); + assertEquals(1, rs.getObject(1)); + assertEquals(1, rs.getObject(2)); + assertTrue(rs.next()); + assertEquals(2, rs.getObject(1)); + assertEquals(null, rs.getObject(2)); + assertTrue(rs.next()); + assertEquals(3, rs.getObject(1)); + assertEquals(10, rs.getObject(2)); + assertFalse(rs.next()); + + rs.close(); + conn.close(); + } + + @Test + public void testDefaultLocalIndexed() throws Exception { + String table = generateUniqueName(); + String ddl = "CREATE TABLE IF NOT EXISTS " + table + " (" + + "pk INTEGER PRIMARY KEY," + + "c1 INTEGER," + + "c2 INTEGER DEFAULT 100)"; + + Connection conn = DriverManager.getConnection(getUrl()); + conn.createStatement().execute(ddl); + conn.commit(); + + String idx = generateUniqueName(); + ddl = "CREATE LOCAL INDEX " + idx + " on " + table + " (c2)"; + conn.createStatement().execute(ddl); + conn.commit(); + + String dml = "UPSERT INTO " + table + " (pk, c1) VALUES (1, 2)"; + conn.createStatement().execute(dml); + conn.commit(); + + ResultSet rs = + conn.createStatement().executeQuery("SELECT c2 FROM " + table + " WHERE c2 = 100"); + assertTrue(rs.next()); + assertEquals(100, rs.getInt(1)); + assertFalse(rs.next()); + + rs = conn.createStatement().executeQuery("SELECT c2 FROM " + table + " WHERE c2 = 5"); + assertFalse(rs.next()); + } + + @Test + public void testDefaultIndexed() throws Exception { + String table = generateUniqueName(); + String ddl = "CREATE TABLE IF NOT EXISTS " + table + " (" + + "pk INTEGER PRIMARY KEY," + + "c1 INTEGER," + + "c2 INTEGER DEFAULT 100)"; + + Connection conn = DriverManager.getConnection(getUrl()); + conn.createStatement().execute(ddl); + conn.commit(); + + String idx = generateUniqueName(); + ddl = "CREATE INDEX " + idx + " on " + table + " (c2)"; + conn.createStatement().execute(ddl); + conn.commit(); + + String dml = "UPSERT INTO " + table + " (pk, c1) VALUES (1, 2)"; + conn.createStatement().execute(dml); + conn.commit(); + + ResultSet rs = + conn.createStatement().executeQuery("SELECT c2 FROM " + table + " WHERE c2 = 100"); + assertTrue(rs.next()); + assertEquals(100, rs.getInt(1)); + assertFalse(rs.next()); + + rs = conn.createStatement().executeQuery("SELECT c2 FROM " + table + " WHERE c2 = 5"); + assertFalse(rs.next()); + } + + @Test + public void testDefaultColumnValue() throws Exception { + String sharedTable1 = generateUniqueName(); + String ddl = "CREATE TABLE IF NOT EXISTS " + sharedTable1 + " (" + + "pk1 INTEGER NOT NULL, " + + "pk2 INTEGER DEFAULT 10, " + + "CONSTRAINT NAME_PK PRIMARY KEY (pk1))"; + + Connection conn = DriverManager.getConnection(getUrl()); + conn.createStatement().execute(ddl); + + String dml = "UPSERT INTO " + sharedTable1 + " VALUES (1, 1)"; + conn.createStatement().execute(dml); + dml = "UPSERT INTO " + sharedTable1 + " VALUES (2, null)"; + conn.createStatement().execute(dml); + dml = "UPSERT INTO " + sharedTable1 + " VALUES (3)"; + conn.createStatement().execute(dml); + conn.commit(); + + + String projection = "*"; + + ResultSet rs = conn.createStatement() + .executeQuery("SELECT " + projection + " FROM " + sharedTable1 + " WHERE pk1 = 1"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals(1, rs.getInt(2)); + assertFalse(rs.next()); + + rs = conn.createStatement() + .executeQuery("SELECT " + projection + " FROM " + sharedTable1 + " WHERE pk1 = 2"); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertEquals(null, rs.getString(2)); + assertFalse(rs.next()); + + rs = conn.createStatement() + .executeQuery("SELECT " + projection + " FROM " + sharedTable1 + " WHERE pk1 = 3"); + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + assertEquals(10, rs.getInt(2)); + assertFalse(rs.next()); + } + +} \ No newline at end of file diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/KeyValueColumnExpression.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/KeyValueColumnExpression.java index 62665e4..f6a9e04 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/expression/KeyValueColumnExpression.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/KeyValueColumnExpression.java @@ -28,6 +28,7 @@ import org.apache.phoenix.expression.visitor.ExpressionVisitor; import org.apache.phoenix.schema.PColumn; import org.apache.phoenix.schema.PDatum; import org.apache.phoenix.schema.tuple.Tuple; +import org.apache.phoenix.schema.tuple.ValueGetterTuple; import org.apache.phoenix.util.SchemaUtil; @@ -115,6 +116,14 @@ public class KeyValueColumnExpression extends ColumnExpression { return tuple.getValue(cf, cq, ptr); } + public boolean evaluateUnsafe(Tuple tuple, ImmutableBytesWritable ptr) { + if (tuple instanceof ValueGetterTuple) { + return ((ValueGetterTuple) tuple).getValueUnsafe(cf, cq, ptr); + } else { + return tuple.getValue(cf, cq, ptr); + } + } + @Override public void readFields(DataInput input) throws IOException { super.readFields(input); diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/SingleCellColumnExpression.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/SingleCellColumnExpression.java index 16f08d8..2c15297 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/expression/SingleCellColumnExpression.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/SingleCellColumnExpression.java @@ -91,14 +91,32 @@ public class SingleCellColumnExpression extends KeyValueColumnExpression { } else if (ptr.getLength() == 0) { return true; } - // the first position is reserved and we offset maxEncodedColumnQualifier by ENCODED_CQ_COUNTER_INITIAL_VALUE (which is the minimum encoded column qualifier) - int index = decodedColumnQualifier-QueryConstants.ENCODED_CQ_COUNTER_INITIAL_VALUE+1; - // Given a ptr to the entire array, set ptr to point to a particular element within that array + // the first position is reserved and we offset maxEncodedColumnQualifier by + // ENCODED_CQ_COUNTER_INITIAL_VALUE (which is the minimum encoded column qualifier) + int index = decodedColumnQualifier - QueryConstants.ENCODED_CQ_COUNTER_INITIAL_VALUE + 1; + // Given a ptr to the entire array, set ptr to point to a particular element + // within that array ColumnValueDecoder encoderDecoder = immutableStorageScheme.getDecoder(); return encoderDecoder.decode(ptr, index); } @Override + public boolean evaluateUnsafe(Tuple tuple, ImmutableBytesWritable ptr) { + if (!super.evaluateUnsafe(tuple, ptr)) { + return false; + } else if (ptr.getLength() == 0) { + return true; + } + // the first position is reserved and we offset maxEncodedColumnQualifier by + // ENCODED_CQ_COUNTER_INITIAL_VALUE (which is the minimum encoded column qualifier) + int index = decodedColumnQualifier - QueryConstants.ENCODED_CQ_COUNTER_INITIAL_VALUE + 1; + // Given a ptr to the entire array, set ptr to point to a particular element + // within that array + ColumnValueDecoder encoderDecoder = immutableStorageScheme.getDecoder(); + return encoderDecoder.decode(ptr, index); + } + + @Override public void readFields(DataInput input) throws IOException { super.readFields(input); this.decodedColumnQualifier = WritableUtils.readVInt(input); diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/DefaultValueExpression.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/DefaultValueExpression.java index fceb442..bf27df4 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/DefaultValueExpression.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/DefaultValueExpression.java @@ -22,6 +22,8 @@ import java.util.List; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.phoenix.expression.Expression; +import org.apache.phoenix.expression.KeyValueColumnExpression; +import org.apache.phoenix.expression.SingleCellColumnExpression; import org.apache.phoenix.schema.tuple.Tuple; import org.apache.phoenix.schema.types.PDataType; @@ -44,7 +46,15 @@ public class DefaultValueExpression extends ScalarFunction { @Override public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) { - boolean evaluated = children.get(0).evaluate(tuple, ptr); + Expression firstChild = children.get(0); + boolean evaluated; + if (firstChild instanceof SingleCellColumnExpression) { + evaluated = ((SingleCellColumnExpression) firstChild).evaluateUnsafe(tuple, ptr); + } else if (firstChild instanceof KeyValueColumnExpression) { + evaluated = ((KeyValueColumnExpression) firstChild).evaluateUnsafe(tuple, ptr); + } else { + evaluated = children.get(0).evaluate(tuple, ptr); + } if (evaluated) { // Will potentially evaluate to null without evaluating the second expression return true; diff --git a/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/AbstractValueGetter.java b/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/AbstractValueGetter.java index 08d19d0..90f9094 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/AbstractValueGetter.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/AbstractValueGetter.java @@ -33,7 +33,9 @@ public abstract class AbstractValueGetter implements ValueGetter{ int valueOffset = 0; int valueLength = 0; byte[] valueBytes = HConstants.EMPTY_BYTE_ARRAY; - if (value != null) { + if (value == null) { + return null; + } else { valueBytes = value.get(); valueOffset = value.getOffset(); valueLength = value.getLength(); diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java index 32270ab..511f859 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java @@ -1637,9 +1637,6 @@ public class MetaDataClient { String columnFamilyName = column.getFamilyName()!=null ? column.getFamilyName().getString() : null; colName = ColumnName.caseSensitiveColumnName(IndexUtil.getIndexColumnName(columnFamilyName, column.getName().getString())); isRowTimestamp = column.isRowTimestamp(); - if (colRef.getColumn().getExpressionStr() != null) { - expressionStr = colRef.getColumn().getExpressionStr(); - } } else { // if this is an expression @@ -3794,7 +3791,7 @@ public class MetaDataClient { // if cascade keyword is passed and indexes are provided either implicitly or explicitly if (cascade && (indexes == null || !indexes.isEmpty())) { indexesPTable = getIndexesPTableForCascade(indexes, table); - if(indexesPTable.size() == 0) { + if (indexesPTable.size() == 0) { // go back to regular behavior of altering the table/view cascade = false; } else { @@ -4759,7 +4756,7 @@ public class MetaDataClient { try { if (newIndexState == PIndexState.ACTIVE){ tableUpsert = connection.prepareStatement(UPDATE_INDEX_STATE_TO_ACTIVE); - }else{ + } else { tableUpsert = connection.prepareStatement(UPDATE_INDEX_STATE); } tableUpsert.setString(1, connection.getTenantId() == null ? null : connection.getTenantId().getString()); @@ -5518,8 +5515,10 @@ public class MetaDataClient { if (changePermsStatement.getSchemaName() != null) { // SYSTEM.CATALOG doesn't have any entry for "default" HBase namespace, hence we will bypass the check - if(!changePermsStatement.getSchemaName().equals(SchemaUtil.SCHEMA_FOR_DEFAULT_NAMESPACE)) { - FromCompiler.getResolverForSchema(changePermsStatement.getSchemaName(), connection); + if (!changePermsStatement.getSchemaName() + .equals(SchemaUtil.SCHEMA_FOR_DEFAULT_NAMESPACE)) { + FromCompiler.getResolverForSchema(changePermsStatement.getSchemaName(), + connection); } changePermsOnSchema(clusterConnection, changePermsStatement); @@ -5552,7 +5551,7 @@ public class MetaDataClient { } private void changePermsOnSchema(ClusterConnection clusterConnection, ChangePermsStatement changePermsStatement) throws Throwable { - if(changePermsStatement.isGrantStatement()) { + if (changePermsStatement.isGrantStatement()) { AccessControlClient.grant(clusterConnection, changePermsStatement.getSchemaName(), changePermsStatement.getName(), changePermsStatement.getPermsList()); } else { AccessControlClient.revoke(clusterConnection, changePermsStatement.getSchemaName(), changePermsStatement.getName(), Permission.Action.values()); @@ -5569,14 +5568,14 @@ public class MetaDataClient { boolean schemaInconsistency = false; List<PTable> inconsistentTables = null; - for(PTable indexTable : inputTable.getIndexes()) { + for (PTable indexTable : inputTable.getIndexes()) { // Local Indexes don't correspond to new physical table, they are just stored in separate CF of base table. - if(indexTable.getIndexType().equals(IndexType.LOCAL)) { + if (indexTable.getIndexType().equals(IndexType.LOCAL)) { continue; } if (inputTable.isNamespaceMapped() != indexTable.isNamespaceMapped()) { schemaInconsistency = true; - if(inconsistentTables == null) { + if (inconsistentTables == null) { inconsistentTables = new ArrayList<>(); } inconsistentTables.add(indexTable); @@ -5588,8 +5587,8 @@ public class MetaDataClient { changePermsOnTable(clusterConnection, changePermsStatement, tableName); } - if(schemaInconsistency) { - for(PTable table : inconsistentTables) { + if (schemaInconsistency) { + for (PTable table : inconsistentTables) { LOGGER.error("Fail to propagate permissions to Index Table: " + table.getName()); } throw new TablesNotInSyncException(inputTable.getTableName().getString(), @@ -5600,12 +5599,12 @@ public class MetaDataClient { byte[] viewIndexTableBytes = MetaDataUtil.getViewIndexPhysicalName(inputTable.getPhysicalName().getBytes()); tableName = org.apache.hadoop.hbase.TableName.valueOf(viewIndexTableBytes); boolean viewIndexTableExists = admin.tableExists(tableName); - if(viewIndexTableExists) { + if (viewIndexTableExists) { LOGGER.info("Updating permissions for View Index Table: " + Bytes.toString(viewIndexTableBytes) + " Base Table: " + inputTable.getName()); changePermsOnTable(clusterConnection, changePermsStatement, tableName); } else { - if(inputTable.isMultiTenant()) { + if (inputTable.isMultiTenant()) { LOGGER.error("View Index Table not found for MultiTenant Table: " + inputTable.getName()); LOGGER.error("Fail to propagate permissions to view Index Table: " + tableName.getNameAsString()); throw new TablesNotInSyncException(inputTable.getTableName().getString(), @@ -5616,7 +5615,7 @@ public class MetaDataClient { private void changePermsOnTable(ClusterConnection clusterConnection, ChangePermsStatement changePermsStatement, org.apache.hadoop.hbase.TableName tableName) throws Throwable { - if(changePermsStatement.isGrantStatement()) { + if (changePermsStatement.isGrantStatement()) { AccessControlClient.grant(clusterConnection, tableName, changePermsStatement.getName(), null, null, changePermsStatement.getPermsList()); } else { @@ -5627,7 +5626,7 @@ public class MetaDataClient { private void changePermsOnUser(ClusterConnection clusterConnection, ChangePermsStatement changePermsStatement) throws Throwable { - if(changePermsStatement.isGrantStatement()) { + if (changePermsStatement.isGrantStatement()) { AccessControlClient.grant(clusterConnection, changePermsStatement.getName(), changePermsStatement.getPermsList()); } else { AccessControlClient.revoke(clusterConnection, changePermsStatement.getName(), Permission.Action.values()); diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/tuple/ValueGetterTuple.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/tuple/ValueGetterTuple.java index 833e9f9..e25be80 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/tuple/ValueGetterTuple.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/tuple/ValueGetterTuple.java @@ -55,16 +55,20 @@ public class ValueGetterTuple extends BaseTuple { return true; } - @Override - public KeyValue getValue(byte[] family, byte[] qualifier) { + public KeyValue getValueUnsafe(byte[] family, byte[] qualifier) { try { - KeyValue kv = valueGetter.getLatestKeyValue(new ColumnReference(family, qualifier), ts); - if (kv != null) { - return kv; - } + return valueGetter.getLatestKeyValue(new ColumnReference(family, qualifier), ts); } catch (IOException e) { throw new RuntimeException(e); } + } + + @Override + public KeyValue getValue(byte[] family, byte[] qualifier) { + KeyValue kv = getValueUnsafe(family, qualifier); + if (kv != null) { + return kv; + } byte[] rowKey = valueGetter.getRowKey(); byte[] valueBytes = HConstants.EMPTY_BYTE_ARRAY; return new KeyValue(rowKey, 0, rowKey.length, family, 0, family.length, qualifier, 0, qualifier.length, ts, Type.Put, valueBytes, 0, 0); @@ -89,8 +93,19 @@ public class ValueGetterTuple extends BaseTuple { public boolean getValue(byte[] family, byte[] qualifier, ImmutableBytesWritable ptr) { KeyValue kv = getValue(family, qualifier); - if (kv == null) + if (kv == null) { return false; + } + ptr.set(kv.getValueArray(), kv.getValueOffset(), kv.getValueLength()); + return true; + } + + public boolean getValueUnsafe(byte[] family, byte[] qualifier, + ImmutableBytesWritable ptr) { + KeyValue kv = getValueUnsafe(family, qualifier); + if (kv == null) { + return false; + } ptr.set(kv.getValueArray(), kv.getValueOffset(), kv.getValueLength()); return true; }