diff --git a/bigtable-client-core-parent/bigtable-hbase/src/main/java/com/google/cloud/bigtable/hbase/adapters/Adapters.java b/bigtable-client-core-parent/bigtable-hbase/src/main/java/com/google/cloud/bigtable/hbase/adapters/Adapters.java index 217762d341..a936cf4c54 100644 --- a/bigtable-client-core-parent/bigtable-hbase/src/main/java/com/google/cloud/bigtable/hbase/adapters/Adapters.java +++ b/bigtable-client-core-parent/bigtable-hbase/src/main/java/com/google/cloud/bigtable/hbase/adapters/Adapters.java @@ -15,6 +15,7 @@ */ package com.google.cloud.bigtable.hbase.adapters; +import com.google.cloud.bigtable.hbase.adapters.read.ModelRowAdapter; import com.google.cloud.bigtable.hbase.adapters.read.RowRangeAdapter; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.client.Append; @@ -38,6 +39,8 @@ public final class Adapters { /** Constant ROW_ADAPTER */ public static final RowAdapter ROW_ADAPTER = new RowAdapter(); + /** Constant MODEL_ROW_ADAPTER */ + public static final ModelRowAdapter MODEL_ROW_ADAPTER = new ModelRowAdapter(); /** Constant FLAT_ROW_ADAPTER */ public static final FlatRowAdapter FLAT_ROW_ADAPTER = new FlatRowAdapter(); /** Constant APPEND_ADAPTER */ diff --git a/bigtable-client-core-parent/bigtable-hbase/src/main/java/com/google/cloud/bigtable/hbase/adapters/read/ModelRowAdapter.java b/bigtable-client-core-parent/bigtable-hbase/src/main/java/com/google/cloud/bigtable/hbase/adapters/read/ModelRowAdapter.java new file mode 100644 index 0000000000..9d4d07260b --- /dev/null +++ b/bigtable-client-core-parent/bigtable-hbase/src/main/java/com/google/cloud/bigtable/hbase/adapters/read/ModelRowAdapter.java @@ -0,0 +1,65 @@ +/* + * Copyright 2019 Google LLC. + * + * Licensed 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 com.google.cloud.bigtable.hbase.adapters.read; + +import com.google.cloud.bigtable.data.v2.models.Row; +import com.google.cloud.bigtable.hbase.adapters.ResponseAdapter; +import com.google.cloud.bigtable.hbase.util.ByteStringer; +import com.google.cloud.bigtable.hbase.util.TimestampConverter; +import com.google.common.collect.ImmutableList; +import java.util.SortedSet; +import java.util.TreeSet; +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.KeyValue; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.util.Bytes; + +/** + * Adapt between a {@link Row} and an hbase client {@link Result}. + */ +public class ModelRowAdapter implements ResponseAdapter { + + @Override + public Result adaptResponse(Row response) { + if (response == null) { + return new Result(); + } + + ImmutableList.Builder hbaseCellBuilder = ImmutableList.builder(); + byte[] rowKey = ByteStringer.extract(response.getKey()); + for (com.google.cloud.bigtable.data.v2.models.RowCell rowCell : response.getCells()) { + // Cells with labels are for internal use, do not return them. + // TODO(kevinsi4508): Filter out targeted {@link WhileMatchFilter} labels. + if (rowCell.getLabels().size() > 0) { + continue; + } + byte[] familyNameBytes = Bytes.toBytes(rowCell.getFamily()); + byte[] columnQualifier = ByteStringer.extract(rowCell.getQualifier()); + long hbaseTimestamp = TimestampConverter.bigtable2hbase(rowCell.getTimestamp()); + RowCell keyValue = new RowCell( + rowKey, + familyNameBytes, + columnQualifier, + hbaseTimestamp, + ByteStringer.extract(rowCell.getValue())); + + hbaseCellBuilder.add(keyValue); + } + + ImmutableList hbaseCells = hbaseCellBuilder.build(); + return Result.create(hbaseCells.toArray(new Cell[hbaseCells.size()])); + } +} diff --git a/bigtable-client-core-parent/bigtable-hbase/src/test/java/com/google/cloud/bigtable/hbase/adapters/read/TestModelRowAdapter.java b/bigtable-client-core-parent/bigtable-hbase/src/test/java/com/google/cloud/bigtable/hbase/adapters/read/TestModelRowAdapter.java new file mode 100644 index 0000000000..cb3459ec02 --- /dev/null +++ b/bigtable-client-core-parent/bigtable-hbase/src/test/java/com/google/cloud/bigtable/hbase/adapters/read/TestModelRowAdapter.java @@ -0,0 +1,103 @@ +/* + * Copyright 2019 Google LLC. + * + * Licensed 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 com.google.cloud.bigtable.hbase.adapters.read; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.google.cloud.bigtable.data.v2.models.Row; +import com.google.cloud.bigtable.data.v2.models.RowCell; +import com.google.cloud.bigtable.hbase.util.ByteStringer; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.ByteString; +import java.util.Collections; +import java.util.List; +import org.apache.hadoop.hbase.client.Result; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for the {@link ModelRowAdapter}. + */ +@RunWith(JUnit4.class) +public class TestModelRowAdapter { + + private static final String FAMILY_1 = "firstFamily"; + private static final String FAMILY_2 = "secondFamily"; + private static final ByteString QUALIFIER_1 = ByteString.copyFromUtf8("qualifier1"); + private static final ByteString QUALIFIER_2 = ByteString.copyFromUtf8("qualifier2"); + private static final long TIMESTAMP_MS_1 = 12345600; + private static final long TIMESTAMP_MS_2 = 65432100; + private static final long TIMESTAMP_MS_3 = 92929292; + private static final long TIMESTAMP_MILLS_1 = TIMESTAMP_MS_1/1000; + private static final long TIMESTAMP_MILLS_2 = TIMESTAMP_MS_2/1000; + private static final String LABEL = "label"; + private static final List LABEL_LIST = Collections.emptyList(); + private static final ByteString VALUE_1 = ByteString.copyFromUtf8("test-value-1"); + private static final ByteString VALUE_2 = ByteString.copyFromUtf8("test-value-2"); + private static final ByteString VALUE_3 = ByteString.copyFromUtf8("test-value-3"); + private static final ByteString VALUE_4 = ByteString.copyFromUtf8("test-value-4"); + private static final ByteString ROW_KEY = ByteString.copyFromUtf8("test-key"); + + private ModelRowAdapter adapter = new ModelRowAdapter(); + + @Test + public void testAdaptResponseWhenNull(){ + Result result = adapter.adaptResponse(null); + assertNull(result.rawCells()); + } + + @Test + public void testAdaptResWithEmptyRow(){ + Row row = Row.create(ROW_KEY, Collections.emptyList()); + Result result = adapter.adaptResponse(row); + assertEquals(0, result.rawCells().length); + } + + @Test + public void testAdaptResWithMultipleRow(){ + ImmutableList.Builder rowCells = ImmutableList.builder(); + rowCells.add(RowCell.create(FAMILY_1, QUALIFIER_1, TIMESTAMP_MS_1, LABEL_LIST, VALUE_1)); + rowCells.add(RowCell.create(FAMILY_1, QUALIFIER_2, TIMESTAMP_MS_2, LABEL_LIST, VALUE_2)); + rowCells.add(RowCell.create(FAMILY_2, QUALIFIER_2, TIMESTAMP_MS_2, LABEL_LIST, VALUE_3)); + rowCells.add(RowCell.create(FAMILY_2, QUALIFIER_1, TIMESTAMP_MS_1, LABEL_LIST, VALUE_4)); + + //Row containing Label, Should be ignored + rowCells.add(RowCell.create(FAMILY_1, QUALIFIER_2, TIMESTAMP_MS_3, + Collections.singletonList(LABEL), VALUE_1)); + Row inputRow = Row.create(ROW_KEY, rowCells.build()); + + Result result = adapter.adaptResponse(inputRow); + assertEquals(4, result.rawCells().length); + + // Duplicate row and row with label cells should be removed. The timestamp micros gets + // converted to millisecond accuracy. + byte[] keyArray = ByteStringer.extract(ROW_KEY); + org.apache.hadoop.hbase.Cell[] expectedCells = new org.apache.hadoop.hbase.Cell[] { + new com.google.cloud.bigtable.hbase.adapters.read.RowCell(keyArray, FAMILY_1.getBytes(), + QUALIFIER_1.toByteArray(), TIMESTAMP_MILLS_1, VALUE_1.toByteArray()), + new com.google.cloud.bigtable.hbase.adapters.read.RowCell(keyArray, FAMILY_1.getBytes(), + QUALIFIER_2.toByteArray(), TIMESTAMP_MILLS_2, VALUE_2.toByteArray()), + new com.google.cloud.bigtable.hbase.adapters.read.RowCell(keyArray, FAMILY_2.getBytes(), + QUALIFIER_2.toByteArray(), TIMESTAMP_MILLS_2, VALUE_3.toByteArray()), + new com.google.cloud.bigtable.hbase.adapters.read.RowCell(keyArray, FAMILY_2.getBytes(), + QUALIFIER_1.toByteArray(), TIMESTAMP_MILLS_1, VALUE_4.toByteArray()), + }; + assertArrayEquals(expectedCells, result.rawCells()); + } +}