diff --git a/src/main/java/com/google/cloud/anviltop/hbase/AnvilTop.java b/src/main/java/com/google/cloud/anviltop/hbase/AnvilTop.java index ef288a5c2c..88d0c850c4 100644 --- a/src/main/java/com/google/cloud/anviltop/hbase/AnvilTop.java +++ b/src/main/java/com/google/cloud/anviltop/hbase/AnvilTop.java @@ -1,5 +1,3 @@ -package com.google.cloud.anviltop.hbase; - /* * Copyright (c) 2013 Google Inc. * @@ -13,6 +11,7 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ +package com.google.cloud.anviltop.hbase; import com.google.protobuf.Descriptors; import com.google.protobuf.Message; diff --git a/src/main/java/org/apache/hadoop/hbase/client/AnvilTopConnection.java b/src/main/java/org/apache/hadoop/hbase/client/AnvilTopConnection.java index 51d4b5a5f5..b7e2a13f1f 100644 --- a/src/main/java/org/apache/hadoop/hbase/client/AnvilTopConnection.java +++ b/src/main/java/org/apache/hadoop/hbase/client/AnvilTopConnection.java @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2013 Google Inc. + * + * 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. + */ + // Because MasterKeepAliveConnection is default scope, we have to use this package. :-/ package org.apache.hadoop.hbase.client; @@ -31,19 +45,6 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -/* - * Copyright (c) 2013 Google Inc. - * - * 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. - */ public class AnvilTopConnection implements HConnection, Closeable { private static final Log LOG = LogFactory.getLog(AnvilTopConnection.class); diff --git a/src/test/java/com/google/cloud/anviltop/hbase/AbstractTest.java b/src/test/java/com/google/cloud/anviltop/hbase/AbstractTest.java index ae0ec260b8..98b8a800da 100644 --- a/src/test/java/com/google/cloud/anviltop/hbase/AbstractTest.java +++ b/src/test/java/com/google/cloud/anviltop/hbase/AbstractTest.java @@ -1,34 +1,32 @@ +/* + * Copyright (c) 2013 Google Inc. + * + * 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.anviltop.hbase; +import org.apache.commons.lang.RandomStringUtils; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HBaseTestingUtility; -import org.apache.hadoop.hbase.HConstants; -import org.apache.hadoop.hbase.client.AnvilTopConnection; import org.apache.hadoop.hbase.client.HConnection; import org.apache.hadoop.hbase.client.HConnectionManager; import org.apache.hadoop.hbase.util.Bytes; import org.junit.After; import org.junit.AfterClass; -import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; +import javax.validation.constraints.NotNull; import java.io.IOException; -/* - * Copyright (c) 2013 Google Inc. - * - * 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. - */ public abstract class AbstractTest { protected static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); protected HConnection connection; @@ -66,4 +64,45 @@ public HConnection createNewConnection() throws IOException { HConnection newConnection = HConnectionManager.createConnection(conf); return newConnection; } + + protected byte[] randomData(String prefix) { + return Bytes.toBytes(prefix + RandomStringUtils.randomAlphanumeric(8)); + } + + protected byte[][] randomData(String prefix, int count) { + byte[][] result = new byte[count][]; + for (int i = 0; i < count; ++i) { + result[i] = Bytes.toBytes(prefix + RandomStringUtils.randomAlphanumeric(8)); + } + return result; + } + + protected long[] sequentialTimestamps(int count) { + return sequentialTimestamps(count, System.currentTimeMillis()); + } + + protected long[] sequentialTimestamps(int count, long firstValue) { + assert count > 0; + long[] timestamps = new long[count]; + timestamps[0] = firstValue; + for (int i = 1; i < timestamps.length; ++i) { + timestamps[i] = timestamps[0] + i; + } + return timestamps; + } + + protected static class QualifierValue implements Comparable { + protected final byte[] qualifier; + protected final byte[] value; + + public QualifierValue(@NotNull byte[] qualifier, @NotNull byte[] value) { + this.qualifier = qualifier; + this.value = value; + } + + @Override + public int compareTo(QualifierValue qualifierValue) { + return Bytes.compareTo(this.qualifier, qualifierValue.qualifier); + } + } } diff --git a/src/test/java/com/google/cloud/anviltop/hbase/TestAutoFlush.java b/src/test/java/com/google/cloud/anviltop/hbase/TestAutoFlush.java index 2087e53f68..d49693db21 100644 --- a/src/test/java/com/google/cloud/anviltop/hbase/TestAutoFlush.java +++ b/src/test/java/com/google/cloud/anviltop/hbase/TestAutoFlush.java @@ -1,3 +1,16 @@ +/* + * Copyright (c) 2013 Google Inc. + * + * 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.anviltop.hbase; import org.apache.commons.lang.RandomStringUtils; @@ -11,18 +24,11 @@ import java.io.IOException; -/* - * Copyright (c) 2013 Google Inc. - * - * 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 +/** + * Requirement 1.1 - Writes are buffered in the client by default (can be disabled). Buffer size + * can be defined programmatically or configuring the hbase.client.write.buffer property. * - * 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. + * TODO - Test buffer size definitions */ public class TestAutoFlush extends AbstractTest { @Test @@ -59,9 +65,9 @@ public void testAutoFlushOn() throws Exception { private Get quickPutThenGet(HTableInterface tableForWrite) throws IOException { // Set up the tiny write and read - byte[] rowKey = Bytes.toBytes("testrow-" + RandomStringUtils.randomAlphanumeric(8)); - byte[] qualifier = Bytes.toBytes("testQualifier-" + RandomStringUtils.randomAlphanumeric(8)); - byte[] value = Bytes.toBytes("testValue-" + RandomStringUtils.randomAlphanumeric(8)); + byte[] rowKey = randomData("testrow-"); + byte[] qualifier = randomData("testQualifier-"); + byte[] value = randomData("testValue-"); Put put = new Put(rowKey); put.add(COLUMN_FAMILY, qualifier, value); Get get = new Get(rowKey); diff --git a/src/test/java/com/google/cloud/anviltop/hbase/TestBasicOps.java b/src/test/java/com/google/cloud/anviltop/hbase/TestBasicOps.java index 533beb136f..fe6fe05043 100644 --- a/src/test/java/com/google/cloud/anviltop/hbase/TestBasicOps.java +++ b/src/test/java/com/google/cloud/anviltop/hbase/TestBasicOps.java @@ -1,3 +1,16 @@ +/* + * Copyright (c) 2013 Google Inc. + * + * 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.anviltop.hbase; import org.apache.commons.lang.RandomStringUtils; @@ -17,29 +30,22 @@ import java.util.List; import java.util.Random; -/* - * Copyright (c) 2013 Google Inc. - * - * 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. - */ public class TestBasicOps extends AbstractTest { + /** + * Happy path for a single value. + */ @Test public void testPutGetDelete() throws IOException { // Initialize - byte[] rowKey = Bytes.toBytes("testrow-" + RandomStringUtils.randomAlphanumeric(8)); - byte[] testQualifier = Bytes.toBytes("testQualifier-" + RandomStringUtils.randomAlphanumeric(8)); - byte[] testValue = Bytes.toBytes("testValue-" + RandomStringUtils.randomAlphanumeric(8)); + byte[] rowKey = randomData("testrow-"); + byte[] testQualifier = randomData("testQualifier-"); + byte[] testValue = randomData("testValue-"); putGetDeleteExists(rowKey, testQualifier, testValue); } + /** + * Requirement 1.2 - Rowkey, family, qualifer, and value are byte[] + */ @Test public void testBinaryPutGetDelete() throws IOException { // Initialize @@ -50,39 +56,97 @@ public void testBinaryPutGetDelete() throws IOException { random.nextBytes(testQualifier); byte[] testValue = new byte[100]; random.nextBytes(testValue); - // TODO(carterpage) - need to test that column-family can work as raw binary + // TODO(carterpage) - test that column-family can work as raw binary // Put putGetDeleteExists(rowKey, testQualifier, testValue); } + /** + * Requirement 1.9 - Referring to a column without the qualifier implicity sets a special "empty" + * qualifier. + */ @Test public void testNullQualifier() throws IOException { - byte[] rowKey = Bytes.toBytes("testrow-" + RandomStringUtils.randomAlphanumeric(8)); - byte[] testQualifier = null; - byte[] testValue = Bytes.toBytes("testValue-" + RandomStringUtils.randomAlphanumeric(8)); - putGetDeleteExists(rowKey, testQualifier, testValue); + // Initialize values + HTableInterface table = connection.getTable(TABLE_NAME); + byte[] rowKey = randomData("testrow-"); + byte[] testValue = randomData("testValue-"); + + // Insert value with null qualifier + Put put = new Put(rowKey); + put.add(COLUMN_FAMILY, null, testValue); + table.put(put); + + // This is treated the same as an empty String (which is just an empty byte array). + Get get = new Get(rowKey); + get.addColumn(COLUMN_FAMILY, Bytes.toBytes("")); + Result result = table.get(get); + Assert.assertEquals(1, result.size()); + Assert.assertTrue(result.containsColumn(COLUMN_FAMILY, null)); + Assert.assertArrayEquals(testValue, + CellUtil.cloneValue(result.getColumnLatestCell(COLUMN_FAMILY, null))); + + // Get as a null. This should work. + get = new Get(rowKey); + get.addColumn(COLUMN_FAMILY, null); + result = table.get(get); + Assert.assertEquals(1, result.size()); + Assert.assertTrue(result.containsColumn(COLUMN_FAMILY, null)); + Assert.assertArrayEquals(testValue, + CellUtil.cloneValue(result.getColumnLatestCell(COLUMN_FAMILY, null))); + + // This should return when selecting the whole family too. + get = new Get(rowKey); + get.addFamily(COLUMN_FAMILY); + result = table.get(get); + Assert.assertEquals(1, result.size()); + Assert.assertTrue(result.containsColumn(COLUMN_FAMILY, null)); + Assert.assertArrayEquals(testValue, + CellUtil.cloneValue(result.getColumnLatestCell(COLUMN_FAMILY, null))); + + // Delete + Delete delete = new Delete(rowKey); + delete.deleteColumn(COLUMN_FAMILY, null); + table.delete(delete); + + // Confirm deleted + Assert.assertFalse(table.exists(get)); + table.close(); } + /** + * Requirement 2.4 - Maximum cell size is 10MB by default. Can be overriden using + * hbase.client.keyvalue.maxsize property. + * + * Cell size includes value and key info, so the value needs to a bit less than the max to work. + */ @Test public void testPutBigValue() throws IOException { // Initialize variables HTableInterface table = connection.getTable(TABLE_NAME); - byte[] testRowKey = Bytes.toBytes("testrow-" + RandomStringUtils.randomAlphanumeric(8)); - byte[] testQualifier = Bytes.toBytes("testQualifier-" + RandomStringUtils.randomAlphanumeric(8)); - byte[] testValue = new byte[10 * 2 ^ 20]; // 10 MB + byte[] testRowKey = randomData("testrow-"); + byte[] testQualifier = randomData("testQualifier-"); + byte[] testValue = new byte[(10 << 20) - 1024]; // 10 MB - 1kB new Random().nextBytes(testValue); putGetDeleteExists(testRowKey, testQualifier, testValue); } - @Test + /** + * Requirement 2.4 - Maximum cell size is 10MB by default. Can be overriden using + * hbase.client.keyvalue.maxsize property. + * + * Ensure the failure case. + */ + @Test(expected = IllegalArgumentException.class) public void testPutTooBigValue() throws IOException { // Initialize variables HTableInterface table = connection.getTable(TABLE_NAME); - byte[] testRowKey = Bytes.toBytes("testrow-" + RandomStringUtils.randomAlphanumeric(8)); - byte[] testQualifier = Bytes.toBytes("testQualifier-" + RandomStringUtils.randomAlphanumeric(8)); - byte[] testValue = new byte[10 * 2 ^ 20 + 1]; // 10 MB + byte[] testRowKey = randomData("testrow-"); + byte[] testQualifier = randomData("testQualifier-"); + byte[] testValue = new byte[10 << 20]; // 10 MB new Random().nextBytes(testValue); + System.out.println(testValue.length); putGetDeleteExists(testRowKey, testQualifier, testValue); } diff --git a/src/test/java/com/google/cloud/anviltop/hbase/TestCreateTable.java b/src/test/java/com/google/cloud/anviltop/hbase/TestCreateTable.java index 23dd6331a1..22f50b1574 100644 --- a/src/test/java/com/google/cloud/anviltop/hbase/TestCreateTable.java +++ b/src/test/java/com/google/cloud/anviltop/hbase/TestCreateTable.java @@ -1,12 +1,3 @@ -package com.google.cloud.anviltop.hbase; - -import org.apache.commons.lang.RandomStringUtils; -import org.apache.hadoop.hbase.util.Bytes; -import org.junit.Assert; -import org.junit.Test; - -import java.io.IOException; - /* * Copyright (c) 2013 Google Inc. * @@ -20,7 +11,19 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ +package com.google.cloud.anviltop.hbase; + +import org.apache.commons.lang.RandomStringUtils; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; + public class TestCreateTable extends AbstractTest { + /** + * Requirement 1.8 - Table names must match [\w_][\w_\-\.]* + */ @Test public void testTableNames() throws IOException { String[] goodnames = { diff --git a/src/test/java/com/google/cloud/anviltop/hbase/TestDurability.java b/src/test/java/com/google/cloud/anviltop/hbase/TestDurability.java index 90fea90fc4..de186a507e 100644 --- a/src/test/java/com/google/cloud/anviltop/hbase/TestDurability.java +++ b/src/test/java/com/google/cloud/anviltop/hbase/TestDurability.java @@ -1,3 +1,16 @@ +/* + * Copyright (c) 2013 Google Inc. + * + * 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.anviltop.hbase; import org.apache.commons.lang.RandomStringUtils; @@ -17,26 +30,13 @@ import java.io.IOException; import java.util.List; -/* - * Copyright (c) 2013 Google Inc. - * - * 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. - */ public class TestDurability extends AbstractTest { /** - * Bigtable doesn't need durability hints. Its recover and write-throughput is fast enough that - * the benefits are negligible. We do however need to not break if a user provides a durability - * hint. + * Requirement 1.5 - A client can specify the durability guarantee of any mutation. * - * @throws IOException + * Bigtable can do without durability hints. Its recovery and write-throughput is fast enough + * that the benefits are not that great. We do however need to not break if a user provides a + * durability hint. We'll always do a durable write regardless of the hint. */ @Test public void testDurability() throws IOException { @@ -47,9 +47,9 @@ public void testDurability() throws IOException { private void testDurability(Durability durability) throws IOException { // Initialize - byte[] rowKey = Bytes.toBytes("testrow-" + RandomStringUtils.randomAlphanumeric(8)); - byte[] testQualifier = Bytes.toBytes("testQualifier-" + RandomStringUtils.randomAlphanumeric(8)); - byte[] testValue = Bytes.toBytes("testValue-" + RandomStringUtils.randomAlphanumeric(8)); + byte[] rowKey = randomData("testrow-"); + byte[] testQualifier = randomData("testQualifier-"); + byte[] testValue = randomData("testValue-"); // Put Put put = new Put(rowKey); diff --git a/src/test/java/com/google/cloud/anviltop/hbase/TestGet.java b/src/test/java/com/google/cloud/anviltop/hbase/TestGet.java new file mode 100644 index 0000000000..e0c249bcd1 --- /dev/null +++ b/src/test/java/com/google/cloud/anviltop/hbase/TestGet.java @@ -0,0 +1,487 @@ +/* + * Copyright (c) 2013 Google Inc. + * + * 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.anviltop.hbase; + +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.CellUtil; +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException; +import org.apache.hadoop.hbase.regionserver.NoSuchColumnFamilyException; +import org.junit.Assert; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HTableInterface; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class TestGet extends AbstractTest { + /** + * Requirement 3.2 - If a column family is requested, but no qualifier, all columns in that family + * are returned + */ + @Test + public void testNoQualifier() throws IOException { + // Initialize variables + HTableInterface table = connection.getTable(TABLE_NAME); + byte[] rowKey = randomData("testrow-"); + int numValues = 3; + byte[][] quals = randomData("qual-", numValues); + byte[][] values = randomData("value-", numValues); + + // Insert some columns + Put put = new Put(rowKey); + for (int i = 0; i < numValues; ++i) { + put.add(COLUMN_FAMILY, quals[i], values[i]); + } + table.put(put); + + // Get without a qualifer, and confirm all results are returned. + Get get = new Get(rowKey); + get.addFamily(COLUMN_FAMILY); + Result result = table.get(get); + Assert.assertEquals(numValues, result.size()); + for (int i = 0; i < numValues; ++i) { + Assert.assertTrue(result.containsColumn(COLUMN_FAMILY, quals[i])); + Assert.assertArrayEquals(values[i], + CellUtil.cloneValue(result.getColumnLatestCell(COLUMN_FAMILY, quals[i]))); + } + + // Cleanup + Delete delete = new Delete(rowKey); + table.delete(delete); + table.close(); + } + + /** + * Requirement 3.3 - Multiple family:qualifiers can be requested for a single row. + */ + @Test + public void testMultipleQualifiers() throws IOException { + // Initialize variables + HTableInterface table = connection.getTable(TABLE_NAME); + byte[] rowKey = randomData("testrow-"); + int numValues = 3; + byte[][] quals = randomData("qual-", numValues); + byte[][] values = randomData("value-", numValues); + + // Insert a few columns + Put put = new Put(rowKey); + for (int i = 0; i < numValues; ++i) { + put.add(COLUMN_FAMILY, quals[i], values[i]); + } + table.put(put); + + // Select some, but not all columns, and confirm that's what's returned. + Get get = new Get(rowKey); + int[] colsToSelect = { 0, 2 }; + for (int i : colsToSelect) { + get.addColumn(COLUMN_FAMILY, quals[i]); + } + Result result = table.get(get); + Assert.assertEquals(colsToSelect.length, result.size()); + for (int i : colsToSelect) { + Assert.assertTrue(result.containsColumn(COLUMN_FAMILY, quals[i])); + Assert.assertArrayEquals(values[i], + CellUtil.cloneValue(result.getColumnLatestCell(COLUMN_FAMILY, quals[i]))); + } + + // Cleanup + Delete delete = new Delete(rowKey); + table.delete(delete); + table.close(); + } + + /** + * Requirement 3.4 - A time range of minTimestamp (inclusive) - maxTimestamp (exclusive) can be + * specified. + */ + @Test + public void testTimeRange() throws IOException { + // Initialize variables + HTableInterface table = connection.getTable(TABLE_NAME); + byte[] rowKey = randomData("testrow-"); + byte[] qual = randomData("qual-"); + int numVersions = 5; + int minVersion = 1; + int maxVersion = 4; + long timestamps[] = sequentialTimestamps(numVersions); + byte[][] values = randomData("value-", numVersions); + + // Insert values with different timestamps at the same column. + Put put = new Put(rowKey); + for (int i = 0; i < numVersions; ++i) { + put.add(COLUMN_FAMILY, qual, timestamps[i], values[i]); + } + table.put(put); + + // Get with a time range, and return the correct cells are returned. + Get get = new Get(rowKey); + get.addColumn(COLUMN_FAMILY, qual); + get.setTimeRange(timestamps[minVersion], timestamps[maxVersion]); + get.setMaxVersions(numVersions); + Result result = table.get(get); + Assert.assertEquals(maxVersion - minVersion, result.size()); + Assert.assertTrue(result.containsColumn(COLUMN_FAMILY, qual)); + List cells = result.getColumnCells(COLUMN_FAMILY, qual); + Assert.assertEquals(maxVersion - minVersion, cells.size()); + + // Cells return in descending order. Max is exclusive, min is inclusive. + for (int i = maxVersion - 1, j = 0; i >= minVersion; --i, ++j) { + Assert.assertEquals(timestamps[i], cells.get(j).getTimestamp()); + Assert.assertArrayEquals(values[i], CellUtil.cloneValue(cells.get(j))); + } + + // Cleanup + Delete delete = new Delete(rowKey); + table.delete(delete); + table.close(); + } + + /** + * Requirement 3.5 - A single timestamp can be specified. + */ + @Test + public void testSingleTimestamp() throws IOException { + // Initialize variables + HTableInterface table = connection.getTable(TABLE_NAME); + byte[] rowKey = randomData("testrow-"); + byte[] qual = randomData("qual-"); + int numVersions = 5; + int particularTimestamp = 3; + long timestamps[] = sequentialTimestamps(numVersions); + byte[][] values = randomData("value-", numVersions); + + // Insert several timestamps for a single row/column. + Put put = new Put(rowKey); + for (int i = 0; i < numVersions; ++i) { + put.add(COLUMN_FAMILY, qual, timestamps[i], values[i]); + } + table.put(put); + + // Get a particular timestamp, and confirm it's returned. + Get get = new Get(rowKey); + get.addColumn(COLUMN_FAMILY, qual); + get.setTimeStamp(timestamps[particularTimestamp]); + get.setMaxVersions(numVersions); + Result result = table.get(get); + Assert.assertEquals(1, result.size()); + Assert.assertTrue(result.containsColumn(COLUMN_FAMILY, qual)); + List cells = result.getColumnCells(COLUMN_FAMILY, qual); + Assert.assertEquals(1, cells.size()); + Assert.assertEquals(timestamps[particularTimestamp], cells.get(0).getTimestamp()); + Assert.assertArrayEquals(values[particularTimestamp], CellUtil.cloneValue(cells.get(0))); + + // Cleanup + Delete delete = new Delete(rowKey); + table.delete(delete); + table.close(); + } + + /** + * Requirement 3.6 - Client can request a maximum # of most recent versions returned. + */ + @Test + public void testMaxVersions() throws IOException { + // Initialize data + HTableInterface table = connection.getTable(TABLE_NAME); + byte[] rowKey = randomData("testrow-"); + byte[] qual = randomData("qual-"); + int totalVersions = 5; + int maxVersions = 3; + long timestamps[] = sequentialTimestamps(totalVersions); + byte[][] values = randomData("value-", totalVersions); + + // Insert several versions into the same row/col + Put put = new Put(rowKey); + for (int i = 0; i < totalVersions; ++i) { + put.add(COLUMN_FAMILY, qual, timestamps[i], values[i]); + } + table.put(put); + + // Get with maxVersions and confirm we get the last N versions. + Get get = new Get(rowKey); + get.addColumn(COLUMN_FAMILY, qual); + get.setMaxVersions(maxVersions); + Result result = table.get(get); + Assert.assertEquals(maxVersions, result.size()); + Assert.assertTrue(result.containsColumn(COLUMN_FAMILY, qual)); + List cells = result.getColumnCells(COLUMN_FAMILY, qual); + Assert.assertEquals(maxVersions, cells.size()); + + // Cells return in descending order + for (int i = totalVersions - 1, j = 0; j < maxVersions; --i, ++j) { + Assert.assertEquals(timestamps[i], cells.get(j).getTimestamp()); + Assert.assertArrayEquals(values[i], CellUtil.cloneValue(cells.get(j))); + } + + // Cleanup + Delete delete = new Delete(rowKey); + table.delete(delete); + table.close(); + } + + /** + * Requirement 3.7 - Specify maximum # of results to return per row, per column family. + * + * Requirement 3.8 - Can specify an offset to skip the first N qualifiers in a column family. + */ + @Test + public void testMaxResultsPerColumnFamily() throws IOException { + // Initialize data + HTableInterface table = connection.getTable(TABLE_NAME); + byte[] rowKey = randomData("testrow-"); + int totalColumns = 10; + int offsetColumn = 3; + int maxColumns = 5; + byte[][] quals = randomData("qual-", totalColumns); + byte[][] values = randomData("value-", totalColumns); + long[] timestamps = sequentialTimestamps(totalColumns); + + // Insert a bunch of columns. + Put put = new Put(rowKey); + List keyValues = new ArrayList(); + for (int i = 0; i < totalColumns; ++i) { + put.add(COLUMN_FAMILY, quals[i], timestamps[i], values[i]); + + // Insert multiple timestamps per row/column to ensure only one cell per column is returned. + put.add(COLUMN_FAMILY, quals[i], timestamps[i] - 1, values[i]); + put.add(COLUMN_FAMILY, quals[i], timestamps[i] - 2, values[i]); + + keyValues.add(new QualifierValue(quals[i], values[i])); + } + table.put(put); + + // Get max columns with a particular offset. Values should be ordered by qualifier. + Get get = new Get(rowKey); + get.addFamily(COLUMN_FAMILY); + get.setMaxResultsPerColumnFamily(maxColumns); + get.setRowOffsetPerColumnFamily(offsetColumn); + Result result = table.get(get); + Assert.assertEquals(maxColumns, result.size()); + Cell[] cells = result.rawCells(); + Collections.sort(keyValues); + for (int i = 0; i < maxColumns; ++i) { + QualifierValue keyValue = keyValues.get(offsetColumn + i); + Assert.assertArrayEquals(keyValue.qualifier, CellUtil.cloneQualifier(cells[i])); + Assert.assertArrayEquals(keyValue.value, CellUtil.cloneValue(cells[i])); + } + + // Cleanup + Delete delete = new Delete(rowKey); + table.delete(delete); + table.close(); + } + + /** + * Requirement 3.11 - Result can contain empty values. (zero-length byte[]). + */ + @Test + public void testEmptyValues() throws IOException { + // Initialize data + HTableInterface table = connection.getTable(TABLE_NAME); + int numValues = 10; + byte[] rowKey = randomData("testrow-"); + byte[][] quals = randomData("qual-", numValues); + + // Insert empty values. Null and byte[0] are interchangeable for puts (but not gets). + Put put = new Put(rowKey); + for (int i = 0; i < numValues; ++i) { + put.add(COLUMN_FAMILY, quals[i], i % 2 == 1 ? null : new byte[0]); + } + table.put(put); + + // Check values + Get get = new Get(rowKey); + get.addFamily(COLUMN_FAMILY); + Result result = table.get(get); + for (int i = 0; i < numValues; ++i) { + Assert.assertTrue(result.containsColumn(COLUMN_FAMILY, quals[i])); + Assert.assertArrayEquals(new byte[0], result.getValue(COLUMN_FAMILY, quals[i])); + } + + // Cleanup + Delete delete = new Delete(rowKey); + table.delete(delete); + table.close(); + } + + /** + * Requirement 3.12 - Exists tests whether one or more of the row/columns exists, as specified in + * the Get object. + * + * An OR operation. If multiple columns are in the Get, then only one has to exist. + */ + @Test + public void testExists() throws IOException { + // Initialize data + HTableInterface table = connection.getTable(TABLE_NAME); + int numValues = 10; + byte[][] rowKeys = randomData("testrow-", numValues); + byte[][] quals = randomData("qual-", numValues); + byte[][] values = randomData("value-", numValues); + + // Insert a bunch of data + List puts = new ArrayList(numValues); + for (int i = 0; i < numValues; ++i) { + Put put = new Put(rowKeys[i]); + put.add(COLUMN_FAMILY, quals[i], values[0]); + puts.add(put); + } + table.put(puts); + + // Test just keys, both individually and as batch. + List gets = new ArrayList(numValues); + for(int i = 0; i < numValues; ++i) { + Get get = new Get(rowKeys[i]); + Assert.assertTrue(table.exists(get)); + gets.add(get); + } + Boolean[] exists = table.exists(gets); + Assert.assertEquals(numValues, exists.length); + for (int i = 0; i < numValues; ++i) { + Assert.assertTrue(exists[i]); + } + + // Test keys and familes, both individually and as batch. + gets.clear(); + for(int i = 0; i < numValues; ++i) { + Get get = new Get(rowKeys[i]); + get.addFamily(COLUMN_FAMILY); + Assert.assertTrue(table.exists(get)); + gets.add(get); + } + exists = table.exists(gets); + Assert.assertEquals(numValues, exists.length); + for (int i = 0; i < numValues; ++i) { + Assert.assertTrue(exists[i]); + } + + // Test keys and qualifiers, both individually and as batch. + gets.clear(); + for(int i = 0; i < numValues; ++i) { + Get get = new Get(rowKeys[i]); + get.addColumn(COLUMN_FAMILY, quals[i]); + Assert.assertTrue(table.exists(get)); + gets.add(get); + } + exists = table.exists(gets); + Assert.assertEquals(numValues, exists.length); + for (int i = 0; i < numValues; ++i) { + Assert.assertTrue(exists[i]); + } + + // Test bad rows, both individually and as batch + gets.clear(); + for(int i = 0; i < numValues; ++i) { + Get get = new Get(randomData("badRow-")); + Assert.assertFalse(table.exists(get)); + gets.add(get); + } + exists = table.exists(gets); + Assert.assertEquals(numValues, exists.length); + for (int i = 0; i < numValues; ++i) { + Assert.assertFalse(exists[i]); + } + + // Test bad column family, both individually and as batch + gets.clear(); + for(int i = 0; i < numValues; ++i) { + Get get = new Get(rowKeys[i]); + get.addFamily(randomData("badFamily-")); + boolean throwsException = false; + try { + table.exists(get); + } catch (NoSuchColumnFamilyException e) { + throwsException = true; + } + Assert.assertTrue(throwsException); + gets.add(get); + } + boolean throwsException = false; + try { + table.exists(gets); + } catch (RetriesExhaustedWithDetailsException e) { + throwsException = true; + Assert.assertEquals(numValues, e.getNumExceptions()); + } + Assert.assertTrue(throwsException); + + // Test bad qualifier, both individually and as batch + gets.clear(); + for (int i = 0; i < numValues; ++i) { + Get get = new Get(rowKeys[i]); + get.addColumn(COLUMN_FAMILY, randomData("badColumn-")); + Assert.assertFalse(table.exists(get)); + gets.add(get); + } + exists = table.exists(gets); + Assert.assertEquals(numValues, exists.length); + for (int i = 0; i < numValues; ++i) { + Assert.assertFalse(exists[i]); + } + + // Test correct OR behavior on a per-row basis. + gets.clear(); + for (int i = 0; i < numValues; ++i) { + Get get = new Get(rowKeys[i]); + get.addColumn(COLUMN_FAMILY, quals[i]); + get.addColumn(COLUMN_FAMILY, randomData("badColumn-")); + Assert.assertTrue(table.exists(get)); + gets.add(get); + } + exists = table.exists(gets); + Assert.assertEquals(numValues, exists.length); + for (int i = 0; i < numValues; ++i) { + Assert.assertTrue(exists[i]); + } + } + + /** + * Requirement 3.16 - When submitting an array of Get operations, if one fails, they all fail. + */ + @Test + public void testOneBadApple() throws IOException { + // Initialize data + HTableInterface table = connection.getTable(TABLE_NAME); + int numValues = 10; + + // Run a control test. + List gets = new ArrayList(numValues + 1); + for (int i = 0; i < numValues; ++i) { + Get get = new Get(randomData("key-")); + get.addColumn(COLUMN_FAMILY, randomData("qual-")); + gets.add(get); + } + table.get(gets); + + // Now add a poison get. + Get get = new Get(randomData("testRow-")); + get.addColumn(randomData("badFamily-"), randomData("qual-")); + gets.add(get); + + boolean throwsException = false; + try { + table.get(gets); + } catch (RetriesExhaustedWithDetailsException e) { + throwsException = true; + Assert.assertEquals(1, e.getNumExceptions()); + } + Assert.assertTrue("Expected an exception", throwsException); + } +} diff --git a/src/test/java/com/google/cloud/anviltop/hbase/TestGetTable.java b/src/test/java/com/google/cloud/anviltop/hbase/TestGetTable.java index 70e9f0e149..5d4a48c660 100644 --- a/src/test/java/com/google/cloud/anviltop/hbase/TestGetTable.java +++ b/src/test/java/com/google/cloud/anviltop/hbase/TestGetTable.java @@ -1,13 +1,3 @@ -package com.google.cloud.anviltop.hbase; - -import org.apache.hadoop.hbase.TableName; -import org.apache.hadoop.hbase.client.HTableInterface; -import org.apache.hadoop.hbase.util.Bytes; -import org.junit.Assert; -import org.junit.Test; - -import java.util.concurrent.Executors; - /* * Copyright (c) 2013 Google Inc. * @@ -21,6 +11,19 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ +package com.google.cloud.anviltop.hbase; + +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.HTableInterface; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.Executors; + +/** + * These tests check various factory instantiations of the HTableInterface. + */ public class TestGetTable extends AbstractTest { @Test public void testGetTable1() throws Exception { diff --git a/src/test/java/com/google/cloud/anviltop/hbase/TestPut.java b/src/test/java/com/google/cloud/anviltop/hbase/TestPut.java index e6a1e417b5..e533e5f026 100644 --- a/src/test/java/com/google/cloud/anviltop/hbase/TestPut.java +++ b/src/test/java/com/google/cloud/anviltop/hbase/TestPut.java @@ -1,3 +1,16 @@ +/* + * Copyright (c) 2013 Google Inc. + * + * 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.anviltop.hbase; import org.apache.hadoop.hbase.CellUtil; @@ -12,35 +25,16 @@ import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.util.Bytes; -import org.junit.Ignore; import org.junit.Test; import java.io.IOException; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.SortedMap; import java.util.TreeMap; -import javax.validation.constraints.NotNull; - -/* - * Copyright (c) 2013 Google Inc. - * - * 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. - */ public class TestPut extends AbstractTest { static final int NUM_CELLS = 100; static final int NUM_ROWS = 100; @@ -54,16 +48,16 @@ public class TestPut extends AbstractTest { public void testPutMultipleCellsOneRow() throws IOException { // Initialize variables HTableInterface table = connection.getTable(TABLE_NAME); - byte[] rowKey = Bytes.toBytes("testrow-" + RandomStringUtils.random(8)); + byte[] rowKey = randomData("testrow-"); + byte[][] quals = randomData("testQualifier-", NUM_CELLS); + byte[][] values = randomData("testValue-", NUM_CELLS); // Construct put with NUM_CELL random qualifier/value combos Put put = new Put(rowKey); - List insertedValues = new ArrayList(100); + List keyValues = new ArrayList(100); for (int i = 0; i < NUM_CELLS; ++i) { - String qualifier = "testQualifier-" + RandomStringUtils.randomAlphanumeric(8); - String value = "testValue-" + RandomStringUtils.randomAlphanumeric(8); - put.add(COLUMN_FAMILY, Bytes.toBytes(qualifier), Bytes.toBytes(value)); - insertedValues.add(new QualifierAndValue(qualifier, value)); + put.add(COLUMN_FAMILY, quals[i], values[i]); + keyValues.add(new QualifierValue(quals[i], values[i])); } table.put(put); @@ -74,12 +68,10 @@ public void testPutMultipleCellsOneRow() throws IOException { Assert.assertEquals(NUM_CELLS, cells.size()); // Check results in sort order - Collections.sort(insertedValues); + Collections.sort(keyValues); for (int i = 0; i < NUM_CELLS; ++i) { - String qualifier = insertedValues.get(i).qualifier; - String value = insertedValues.get(i).value; - Assert.assertEquals(qualifier, Bytes.toString(CellUtil.cloneQualifier(cells.get(i)))); - Assert.assertEquals(value, Bytes.toString(CellUtil.cloneValue(cells.get(i)))); + Assert.assertArrayEquals(keyValues.get(i).qualifier, CellUtil.cloneQualifier(cells.get(i))); + Assert.assertArrayEquals(keyValues.get(i).value, CellUtil.cloneValue(cells.get(i))); } // Delete @@ -99,55 +91,52 @@ public void testPutMultipleCellsOneRow() throws IOException { public void testPutGetDeleteMultipleRows() throws IOException { // Initialize interface HTableInterface table = connection.getTable(TABLE_NAME); + byte[][] rowKeys = randomData("testrow-", NUM_ROWS); + byte[][] qualifiers = randomData("testQualifier-", NUM_ROWS); + byte[][] values = randomData("testValue-", NUM_ROWS); // Do puts List puts = new ArrayList(NUM_ROWS); - List rowKeys = new ArrayList(NUM_ROWS); - Map insertedKeyValues = new TreeMap(); + List keys = new ArrayList(NUM_ROWS); + Map insertedKeyValues = new TreeMap(); for (int i = 0; i < NUM_ROWS; ++i) { - String rowKey = "testrow-" + RandomStringUtils.randomAlphanumeric(8); - String qualifier = "testQualifier-" + RandomStringUtils.randomAlphanumeric(8); - String value = "testValue-" + RandomStringUtils.randomAlphanumeric(8); - - Put put = new Put(Bytes.toBytes(rowKey)); - put.add(COLUMN_FAMILY, Bytes.toBytes(qualifier), Bytes.toBytes(value)); + Put put = new Put(rowKeys[i]); + put.add(COLUMN_FAMILY, qualifiers[i], values[i]); puts.add(put); - insertedKeyValues.put(rowKey, new QualifierAndValue(qualifier, value)); + String key = Bytes.toString(rowKeys[i]); + keys.add(key); + insertedKeyValues.put(key, new QualifierValue(qualifiers[i], values[i])); } table.put(puts); // Get List gets = new ArrayList(NUM_ROWS); - Collections.shuffle(rowKeys); // Retrieve in random order - for (String rowKey : rowKeys) { - Get get = new Get(Bytes.toBytes(rowKey)); - String qualifier = insertedKeyValues.get(rowKey).qualifier; - get.addColumn(COLUMN_FAMILY, Bytes.toBytes(qualifier)); + Collections.shuffle(keys); // Retrieve in random order + for (String key : keys) { + Get get = new Get(Bytes.toBytes(key)); + get.addColumn(COLUMN_FAMILY, insertedKeyValues.get(key).qualifier); gets.add(get); } Result[] result = table.get(gets); Assert.assertEquals(NUM_ROWS, result.length); for (int i = 0; i < NUM_ROWS; ++i) { - String rowKey = rowKeys.get(i); + String rowKey = keys.get(i); Assert.assertEquals(rowKey, Bytes.toString(result[i].getRow())); - QualifierAndValue entry = insertedKeyValues.get(rowKey); + QualifierValue entry = insertedKeyValues.get(rowKey); String descriptor = "Row " + i + " (" + rowKey + ": "; Assert.assertEquals(descriptor, 1, result[i].size()); - Assert.assertTrue(descriptor, result[i].containsNonEmptyColumn(COLUMN_FAMILY, - Bytes.toBytes(entry.qualifier))); + Assert.assertTrue(descriptor, + result[i].containsNonEmptyColumn(COLUMN_FAMILY, entry.qualifier)); Assert.assertEquals(descriptor, entry.value, - Bytes.toString(CellUtil.cloneValue( - result[i].getColumnCells(COLUMN_FAMILY, Bytes.toBytes(entry.qualifier)).get(0))) + CellUtil.cloneValue(result[i].getColumnCells(COLUMN_FAMILY, entry.qualifier).get(0)) ); } // Delete List deletes = new ArrayList(NUM_ROWS); - for (String rowKey : rowKeys) { - Delete delete = new Delete(Bytes.toBytes(rowKey)); - String qualifier = insertedKeyValues.get(rowKey).qualifier; - delete.deleteColumn(COLUMN_FAMILY, Bytes.toBytes(qualifier)); + for (byte[] rowKey : rowKeys) { + Delete delete = new Delete(rowKey); deletes.add(delete); } table.delete(deletes); @@ -359,19 +348,4 @@ private void multiplePutsOneBad(int numberOfGoodPuts, byte[][] goodkeys, byte[] thrownException.getCause(0) instanceof NoSuchColumnFamilyException); table.close(); } - - private static class QualifierAndValue implements Comparable { - private final String qualifier; - private final String value; - - public QualifierAndValue(@NotNull String qualifier, @NotNull String value) { - this.qualifier = qualifier; - this.value = value; - } - - @Override - public int compareTo(QualifierAndValue qualifierAndValue) { - return qualifier.compareTo(qualifierAndValue.qualifier); - } - } } diff --git a/src/test/java/com/google/cloud/anviltop/hbase/TestTimestamp.java b/src/test/java/com/google/cloud/anviltop/hbase/TestTimestamp.java index 35cadccad3..546773b41d 100644 --- a/src/test/java/com/google/cloud/anviltop/hbase/TestTimestamp.java +++ b/src/test/java/com/google/cloud/anviltop/hbase/TestTimestamp.java @@ -1,3 +1,16 @@ +/* + * Copyright (c) 2013 Google Inc. + * + * 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.anviltop.hbase; import org.apache.commons.lang.RandomStringUtils; @@ -16,78 +29,68 @@ import java.io.IOException; import java.util.List; -/* - * Copyright (c) 2013 Google Inc. - * - * 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. - */ public class TestTimestamp extends AbstractTest { + /** + * Requirement 1.6 - Custom, arbitrary timestamps are supported. + * + * Version numbers (1,2,3,...) are often used by users. Just make sure we support this cleanly. + */ @Test public void testArbitraryTimestamp() throws IOException { // Initialize HTableInterface table = connection.getTable(TABLE_NAME); - byte[] rowKey = Bytes.toBytes("testrow-" + RandomStringUtils.randomAlphanumeric(8)); - byte[] testQualifier = Bytes.toBytes("testQual-" + RandomStringUtils.randomAlphanumeric(8)); - String testValue1 = "testValue-" + RandomStringUtils.randomAlphanumeric(8); - String testValue2 = "testValue-" + RandomStringUtils.randomAlphanumeric(8); - String testValue3 = "testValue-" + RandomStringUtils.randomAlphanumeric(8); + byte[] rowKey = randomData("testrow-"); + byte[] testQualifier = randomData("testQual-"); + int numVersions = 4; + assert numVersions > 2; + byte[][] values = randomData("testValue-", numVersions); + long[] versions = sequentialTimestamps(numVersions, 1L); - // Put three versions + // Put several versions in the same row/column. Put put = new Put(rowKey); - put.add(COLUMN_FAMILY, testQualifier, 1L, Bytes.toBytes(testValue1)); - put.add(COLUMN_FAMILY, testQualifier, 2L, Bytes.toBytes(testValue2)); - put.add(COLUMN_FAMILY, testQualifier, 3L, Bytes.toBytes(testValue3)); + for (int i = 0; i < numVersions; ++i) { + put.add(COLUMN_FAMILY, testQualifier, versions[i], values[i]); + } table.put(put); - // Confirm they are all here, in descending version number + // Confirm they are all here, in descending order by version number. Get get = new Get(rowKey); get.addColumn(COLUMN_FAMILY, testQualifier); - get.setMaxVersions(5); + get.setMaxVersions(numVersions + 1); Result result = table.get(get); Assert.assertTrue(result.containsColumn(COLUMN_FAMILY, testQualifier)); List cells = result.getColumnCells(COLUMN_FAMILY, testQualifier); - Assert.assertEquals(3, cells.size()); - Assert.assertEquals(3L, cells.get(0).getTimestamp()); - Assert.assertEquals(testValue3, Bytes.toString(CellUtil.cloneValue(cells.get(0)))); - Assert.assertEquals(2L, cells.get(1).getTimestamp()); - Assert.assertEquals(testValue2, Bytes.toString(CellUtil.cloneValue(cells.get(1)))); - Assert.assertEquals(1L, cells.get(2).getTimestamp()); - Assert.assertEquals(testValue1, Bytes.toString(CellUtil.cloneValue(cells.get(2)))); + Assert.assertEquals(numVersions, cells.size()); + for (int i = numVersions - 1, j = 0; j < numVersions; --i, ++j) { + Assert.assertEquals(versions[i], cells.get(j).getTimestamp()); + Assert.assertArrayEquals(values[i], CellUtil.cloneValue(cells.get(j))); + } - // Now limit results to just two versions + // Now limit results to just two versions. get.setMaxVersions(2); result = table.get(get); Assert.assertTrue(result.containsColumn(COLUMN_FAMILY, testQualifier)); cells = result.getColumnCells(COLUMN_FAMILY, testQualifier); Assert.assertEquals(2, cells.size()); - Assert.assertEquals(3L, cells.get(0).getTimestamp()); - Assert.assertEquals(testValue3, Bytes.toString(CellUtil.cloneValue(cells.get(0)))); - Assert.assertEquals(2L, cells.get(1).getTimestamp()); - Assert.assertEquals(testValue2, Bytes.toString(CellUtil.cloneValue(cells.get(1)))); + for (int i = numVersions - 1, j = 0; j < 2; --i, ++j) { + Assert.assertEquals(versions[i], cells.get(j).getTimestamp()); + Assert.assertArrayEquals(values[i], CellUtil.cloneValue(cells.get(j))); + } - // Delete the middle version + // Delete the second-to-last version. Delete delete = new Delete(rowKey); - delete.deleteColumn(COLUMN_FAMILY, testQualifier, 2L); + delete.deleteColumn(COLUMN_FAMILY, testQualifier, versions[numVersions - 2]); table.delete(delete); - // Confirm versions 1 & 3 remain - get.setMaxVersions(5); + // Now, the same get should return the last and third-to-last values. result = table.get(get); Assert.assertTrue(result.containsColumn(COLUMN_FAMILY, testQualifier)); cells = result.getColumnCells(COLUMN_FAMILY, testQualifier); Assert.assertEquals(2, cells.size()); - Assert.assertEquals(3L, cells.get(0).getTimestamp()); - Assert.assertEquals(testValue3, Bytes.toString(CellUtil.cloneValue(cells.get(0)))); - Assert.assertEquals(1L, cells.get(1).getTimestamp()); - Assert.assertEquals(testValue1, Bytes.toString(CellUtil.cloneValue(cells.get(1)))); + Assert.assertEquals(versions[numVersions - 1], cells.get(0).getTimestamp()); + Assert.assertArrayEquals(values[numVersions - 1], CellUtil.cloneValue(cells.get(0))); + Assert.assertEquals(versions[numVersions - 3], cells.get(1).getTimestamp()); + Assert.assertArrayEquals(values[numVersions - 3], CellUtil.cloneValue(cells.get(1))); // Delete row delete = new Delete(rowKey);