001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.types; 019 020import static org.junit.Assert.assertArrayEquals; 021import static org.junit.Assert.assertEquals; 022 023import java.lang.reflect.Constructor; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Comparator; 027import org.apache.hadoop.hbase.HBaseClassTestRule; 028import org.apache.hadoop.hbase.testclassification.MiscTests; 029import org.apache.hadoop.hbase.testclassification.SmallTests; 030import org.apache.hadoop.hbase.util.Bytes; 031import org.apache.hadoop.hbase.util.Order; 032import org.apache.hadoop.hbase.util.PositionedByteRange; 033import org.apache.hadoop.hbase.util.SimplePositionedMutableByteRange; 034import org.junit.ClassRule; 035import org.junit.Test; 036import org.junit.experimental.categories.Category; 037import org.junit.runner.RunWith; 038import org.junit.runners.Parameterized; 039import org.junit.runners.Parameterized.Parameters; 040 041/** 042 * This class both tests and demonstrates how to construct compound rowkeys from a POJO. The code 043 * under test is {@link Struct}. {@link SpecializedPojo1Type1} demonstrates how one might create 044 * their own custom data type extension for an application POJO. 045 */ 046@RunWith(Parameterized.class) 047@Category({ MiscTests.class, SmallTests.class }) 048public class TestStruct { 049 @ClassRule 050 public static final HBaseClassTestRule CLASS_RULE = HBaseClassTestRule.forClass(TestStruct.class); 051 052 @Parameterized.Parameter() 053 public Struct generic; 054 055 @SuppressWarnings("rawtypes") 056 @Parameterized.Parameter(value = 1) 057 public DataType specialized; 058 059 @Parameterized.Parameter(value = 2) 060 public Object[][] constructorArgs; 061 062 @Parameters 063 public static Collection<Object[]> params() { 064 Object[][] pojo1Args = { new Object[] { "foo", 5, 10.001 }, new Object[] { "foo", 100, 7.0 }, 065 new Object[] { "foo", 100, 10.001 }, new Object[] { "bar", 5, 10.001 }, 066 new Object[] { "bar", 100, 10.001 }, new Object[] { "baz", 5, 10.001 }, }; 067 068 Object[][] pojo2Args = 069 { new Object[] { new byte[0], Bytes.toBytes("it"), "was", Bytes.toBytes("the") }, 070 new Object[] { Bytes.toBytes("best"), new byte[0], "of", Bytes.toBytes("times,") }, 071 new Object[] { Bytes.toBytes("it"), Bytes.toBytes("was"), "", Bytes.toBytes("the") }, 072 new Object[] { Bytes.toBytes("worst"), Bytes.toBytes("of"), "times,", new byte[0] }, 073 new Object[] { new byte[0], new byte[0], "", new byte[0] }, }; 074 075 Object[][] params = 076 new Object[][] { { SpecializedPojo1Type1.GENERIC, new SpecializedPojo1Type1(), pojo1Args }, 077 { SpecializedPojo2Type1.GENERIC, new SpecializedPojo2Type1(), pojo2Args }, }; 078 return Arrays.asList(params); 079 } 080 081 static final Comparator<byte[]> NULL_SAFE_BYTES_COMPARATOR = (o1, o2) -> { 082 if (o1 == o2) { 083 return 0; 084 } 085 086 if (null == o1) { 087 return -1; 088 } 089 090 if (null == o2) { 091 return 1; 092 } 093 094 return Bytes.compareTo(o1, o2); 095 }; 096 097 /** 098 * A simple object to serialize. 099 */ 100 private static class Pojo1 implements Comparable<Pojo1> { 101 final String stringFieldAsc; 102 final int intFieldAsc; 103 final double doubleFieldAsc; 104 final transient String str; 105 106 public Pojo1(Object... argv) { 107 stringFieldAsc = (String) argv[0]; 108 intFieldAsc = (Integer) argv[1]; 109 doubleFieldAsc = (Double) argv[2]; 110 str = new StringBuilder().append("{ ").append(null == stringFieldAsc ? "" : "\"") 111 .append(stringFieldAsc).append(null == stringFieldAsc ? "" : "\"").append(", ") 112 .append(intFieldAsc).append(", ").append(doubleFieldAsc).append(" }").toString(); 113 } 114 115 @Override 116 public String toString() { 117 return str; 118 } 119 120 @Override 121 public int compareTo(Pojo1 o) { 122 int cmp = stringFieldAsc.compareTo(o.stringFieldAsc); 123 if (cmp != 0) { 124 return cmp; 125 } 126 cmp = Integer.compare(intFieldAsc, o.intFieldAsc); 127 if (cmp != 0) { 128 return cmp; 129 } 130 return Double.compare(doubleFieldAsc, o.doubleFieldAsc); 131 } 132 133 @Override 134 public int hashCode() { 135 final int prime = 31; 136 int result = 1; 137 long temp; 138 temp = Double.doubleToLongBits(doubleFieldAsc); 139 result = prime * result + (int) (temp ^ (temp >>> 32)); 140 result = prime * result + intFieldAsc; 141 result = prime * result + ((stringFieldAsc == null) ? 0 : stringFieldAsc.hashCode()); 142 return result; 143 } 144 145 @Override 146 public boolean equals(Object obj) { 147 if (this == obj) { 148 return true; 149 } 150 if (!(obj instanceof Pojo1)) { 151 return false; 152 } 153 Pojo1 other = (Pojo1) obj; 154 if ( 155 Double.doubleToLongBits(doubleFieldAsc) != Double.doubleToLongBits(other.doubleFieldAsc) 156 ) { 157 return false; 158 } 159 if (intFieldAsc != other.intFieldAsc) { 160 return false; 161 } 162 if (stringFieldAsc == null) { 163 return other.stringFieldAsc == null; 164 } else { 165 return stringFieldAsc.equals(other.stringFieldAsc); 166 } 167 } 168 } 169 170 /** 171 * A simple object to serialize. 172 */ 173 private static class Pojo2 implements Comparable<Pojo2> { 174 final byte[] byteField1Asc; 175 final byte[] byteField2Dsc; 176 final String stringFieldDsc; 177 final byte[] byteField3Dsc; 178 final transient String str; 179 180 public Pojo2(Object... vals) { 181 byteField1Asc = vals.length > 0 ? (byte[]) vals[0] : null; 182 byteField2Dsc = vals.length > 1 ? (byte[]) vals[1] : null; 183 stringFieldDsc = vals.length > 2 ? (String) vals[2] : null; 184 byteField3Dsc = vals.length > 3 ? (byte[]) vals[3] : null; 185 str = new StringBuilder().append("{ ").append(Bytes.toStringBinary(byteField1Asc)) 186 .append(", ").append(Bytes.toStringBinary(byteField2Dsc)).append(", ") 187 .append(null == stringFieldDsc ? "" : "\"").append(stringFieldDsc) 188 .append(null == stringFieldDsc ? "" : "\"").append(", ") 189 .append(Bytes.toStringBinary(byteField3Dsc)).append(" }").toString(); 190 } 191 192 @Override 193 public String toString() { 194 return str; 195 } 196 197 @Override 198 public int compareTo(Pojo2 o) { 199 int cmp = NULL_SAFE_BYTES_COMPARATOR.compare(byteField1Asc, o.byteField1Asc); 200 if (cmp != 0) { 201 return cmp; 202 } 203 cmp = -NULL_SAFE_BYTES_COMPARATOR.compare(byteField2Dsc, o.byteField2Dsc); 204 if (cmp != 0) { 205 return cmp; 206 } 207 208 if (null == stringFieldDsc) { 209 cmp = 1; 210 } else if (null == o.stringFieldDsc) { 211 cmp = -1; 212 } else if (stringFieldDsc.equals(o.stringFieldDsc)) { 213 cmp = 0; 214 } else { 215 cmp = -stringFieldDsc.compareTo(o.stringFieldDsc); 216 } 217 218 if (cmp != 0) { 219 return cmp; 220 } 221 return -NULL_SAFE_BYTES_COMPARATOR.compare(byteField3Dsc, o.byteField3Dsc); 222 } 223 224 @Override 225 public int hashCode() { 226 final int prime = 31; 227 int result = 1; 228 result = prime * result + Arrays.hashCode(byteField1Asc); 229 result = prime * result + Arrays.hashCode(byteField2Dsc); 230 result = prime * result + Arrays.hashCode(byteField3Dsc); 231 result = prime * result + ((stringFieldDsc == null) ? 0 : stringFieldDsc.hashCode()); 232 return result; 233 } 234 235 @Override 236 public boolean equals(Object obj) { 237 if (this == obj) { 238 return true; 239 } 240 if (!(obj instanceof Pojo2)) { 241 return false; 242 } 243 Pojo2 other = (Pojo2) obj; 244 if (!Arrays.equals(byteField1Asc, other.byteField1Asc)) { 245 return false; 246 } 247 if (!Arrays.equals(byteField2Dsc, other.byteField2Dsc)) { 248 return false; 249 } 250 if (!Arrays.equals(byteField3Dsc, other.byteField3Dsc)) { 251 return false; 252 } 253 if (stringFieldDsc == null) { 254 return other.stringFieldDsc == null; 255 } else { 256 return stringFieldDsc.equals(other.stringFieldDsc); 257 } 258 } 259 } 260 261 /** 262 * A custom data type implementation specialized for {@link Pojo1}. 263 */ 264 private static class SpecializedPojo1Type1 implements DataType<Pojo1> { 265 private static final RawStringTerminated stringField = new RawStringTerminated("/"); 266 private static final RawInteger intField = new RawInteger(); 267 private static final RawDouble doubleField = new RawDouble(); 268 269 /** 270 * The {@link Struct} equivalent of this type. 271 */ 272 public static Struct GENERIC = 273 new StructBuilder().add(stringField).add(intField).add(doubleField).toStruct(); 274 275 @Override 276 public boolean isOrderPreserving() { 277 return true; 278 } 279 280 @Override 281 public Order getOrder() { 282 return null; 283 } 284 285 @Override 286 public boolean isNullable() { 287 return false; 288 } 289 290 @Override 291 public boolean isSkippable() { 292 return true; 293 } 294 295 @Override 296 public int encodedLength(Pojo1 val) { 297 return stringField.encodedLength(val.stringFieldAsc) + intField.encodedLength(val.intFieldAsc) 298 + doubleField.encodedLength(val.doubleFieldAsc); 299 } 300 301 @Override 302 public Class<Pojo1> encodedClass() { 303 return Pojo1.class; 304 } 305 306 @Override 307 public int skip(PositionedByteRange src) { 308 int skipped = stringField.skip(src); 309 skipped += intField.skip(src); 310 skipped += doubleField.skip(src); 311 return skipped; 312 } 313 314 @Override 315 public Pojo1 decode(PositionedByteRange src) { 316 Object[] ret = new Object[3]; 317 ret[0] = stringField.decode(src); 318 ret[1] = intField.decode(src); 319 ret[2] = doubleField.decode(src); 320 return new Pojo1(ret); 321 } 322 323 @Override 324 public int encode(PositionedByteRange dst, Pojo1 val) { 325 int written = stringField.encode(dst, val.stringFieldAsc); 326 written += intField.encode(dst, val.intFieldAsc); 327 written += doubleField.encode(dst, val.doubleFieldAsc); 328 return written; 329 } 330 } 331 332 /** 333 * A custom data type implementation specialized for {@link Pojo2}. 334 */ 335 private static class SpecializedPojo2Type1 implements DataType<Pojo2> { 336 private static RawBytesTerminated byteField1 = new RawBytesTerminated("/"); 337 private static RawBytesTerminated byteField2 = new RawBytesTerminated(Order.DESCENDING, "/"); 338 private static RawStringTerminated stringField = 339 new RawStringTerminated(Order.DESCENDING, new byte[] { 0x00 }); 340 private static RawBytes byteField3 = RawBytes.DESCENDING; 341 342 /** 343 * The {@link Struct} equivalent of this type. 344 */ 345 public static Struct GENERIC = new StructBuilder().add(byteField1).add(byteField2) 346 .add(stringField).add(byteField3).toStruct(); 347 348 @Override 349 public boolean isOrderPreserving() { 350 return true; 351 } 352 353 @Override 354 public Order getOrder() { 355 return null; 356 } 357 358 @Override 359 public boolean isNullable() { 360 return false; 361 } 362 363 @Override 364 public boolean isSkippable() { 365 return true; 366 } 367 368 @Override 369 public int encodedLength(Pojo2 val) { 370 return byteField1.encodedLength(val.byteField1Asc) 371 + byteField2.encodedLength(val.byteField2Dsc) 372 + stringField.encodedLength(val.stringFieldDsc) 373 + byteField3.encodedLength(val.byteField3Dsc); 374 } 375 376 @Override 377 public Class<Pojo2> encodedClass() { 378 return Pojo2.class; 379 } 380 381 @Override 382 public int skip(PositionedByteRange src) { 383 int skipped = byteField1.skip(src); 384 skipped += byteField2.skip(src); 385 skipped += stringField.skip(src); 386 skipped += byteField3.skip(src); 387 return skipped; 388 } 389 390 @Override 391 public Pojo2 decode(PositionedByteRange src) { 392 Object[] ret = new Object[4]; 393 ret[0] = byteField1.decode(src); 394 ret[1] = byteField2.decode(src); 395 ret[2] = stringField.decode(src); 396 ret[3] = byteField3.decode(src); 397 return new Pojo2(ret); 398 } 399 400 @Override 401 public int encode(PositionedByteRange dst, Pojo2 val) { 402 int written = byteField1.encode(dst, val.byteField1Asc); 403 written += byteField2.encode(dst, val.byteField2Dsc); 404 written += stringField.encode(dst, val.stringFieldDsc); 405 written += byteField3.encode(dst, val.byteField3Dsc); 406 return written; 407 } 408 } 409 410 @Test 411 @SuppressWarnings("unchecked") 412 public void testOrderPreservation() throws Exception { 413 Object[] vals = new Object[constructorArgs.length]; 414 PositionedByteRange[] encodedGeneric = new PositionedByteRange[constructorArgs.length]; 415 PositionedByteRange[] encodedSpecialized = new PositionedByteRange[constructorArgs.length]; 416 Constructor<?> ctor = specialized.encodedClass().getConstructor(Object[].class); 417 for (int i = 0; i < vals.length; i++) { 418 vals[i] = ctor.newInstance(new Object[] { constructorArgs[i] }); 419 encodedGeneric[i] = 420 new SimplePositionedMutableByteRange(generic.encodedLength(constructorArgs[i])); 421 encodedSpecialized[i] = 422 new SimplePositionedMutableByteRange(specialized.encodedLength(vals[i])); 423 } 424 425 // populate our arrays 426 for (int i = 0; i < vals.length; i++) { 427 generic.encode(encodedGeneric[i], constructorArgs[i]); 428 encodedGeneric[i].setPosition(0); 429 specialized.encode(encodedSpecialized[i], vals[i]); 430 encodedSpecialized[i].setPosition(0); 431 assertArrayEquals(encodedGeneric[i].getBytes(), encodedSpecialized[i].getBytes()); 432 } 433 434 Arrays.sort(vals); 435 Arrays.sort(encodedGeneric); 436 Arrays.sort(encodedSpecialized); 437 438 for (int i = 0; i < vals.length; i++) { 439 assertEquals("Struct encoder does not preserve sort order at position " + i, vals[i], 440 ctor.newInstance(new Object[] { generic.decode(encodedGeneric[i]) })); 441 assertEquals("Specialized encoder does not preserve sort order at position " + i, vals[i], 442 specialized.decode(encodedSpecialized[i])); 443 } 444 } 445}