1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.types;
19
20 import static org.junit.Assert.assertArrayEquals;
21 import static org.junit.Assert.assertEquals;
22
23 import java.lang.reflect.Constructor;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.Comparator;
27
28 import org.apache.hadoop.hbase.testclassification.SmallTests;
29 import org.apache.hadoop.hbase.util.Bytes;
30 import org.apache.hadoop.hbase.util.Order;
31 import org.apache.hadoop.hbase.util.PositionedByteRange;
32 import org.apache.hadoop.hbase.util.SimplePositionedMutableByteRange;
33 import org.junit.Test;
34 import org.junit.experimental.categories.Category;
35 import org.junit.runner.RunWith;
36 import org.junit.runners.Parameterized;
37 import org.junit.runners.Parameterized.Parameters;
38
39
40
41
42
43
44
45 @RunWith(Parameterized.class)
46 @Category(SmallTests.class)
47 public class TestStruct {
48
49 private Struct generic;
50 @SuppressWarnings("rawtypes")
51 private DataType specialized;
52 private Object[][] constructorArgs;
53
54 public TestStruct(Struct generic, @SuppressWarnings("rawtypes") DataType specialized,
55 Object[][] constructorArgs) {
56 this.generic = generic;
57 this.specialized = specialized;
58 this.constructorArgs = constructorArgs;
59 }
60
61 @Parameters
62 public static Collection<Object[]> params() {
63 Object[][] pojo1Args = {
64 new Object[] { "foo", 5, 10.001 },
65 new Object[] { "foo", 100, 7.0 },
66 new Object[] { "foo", 100, 10.001 },
67 new Object[] { "bar", 5, 10.001 },
68 new Object[] { "bar", 100, 10.001 },
69 new Object[] { "baz", 5, 10.001 },
70 };
71
72 Object[][] pojo2Args = {
73 new Object[] { new byte[0], Bytes.toBytes("it"), "was", Bytes.toBytes("the") },
74 new Object[] { Bytes.toBytes("best"), new byte[0], "of", Bytes.toBytes("times,") },
75 new Object[] { Bytes.toBytes("it"), Bytes.toBytes("was"), "", Bytes.toBytes("the") },
76 new Object[] { Bytes.toBytes("worst"), Bytes.toBytes("of"), "times,", new byte[0] },
77 new Object[] { new byte[0], new byte[0], "", new byte[0] },
78 };
79
80 Object[][] params = new Object[][] {
81 { SpecializedPojo1Type1.GENERIC, new SpecializedPojo1Type1(), pojo1Args },
82 { SpecializedPojo2Type1.GENERIC, new SpecializedPojo2Type1(), pojo2Args },
83 };
84 return Arrays.asList(params);
85 }
86
87 static final Comparator<byte[]> NULL_SAFE_BYTES_COMPARATOR =
88 new Comparator<byte[]>() {
89 @Override
90 public int compare(byte[] o1, byte[] o2) {
91 if (o1 == o2) return 0;
92 if (null == o1) return -1;
93 if (null == o2) return 1;
94 return Bytes.compareTo(o1, o2);
95 }
96 };
97
98
99
100
101 private static class Pojo1 implements Comparable<Pojo1> {
102 final String stringFieldAsc;
103 final int intFieldAsc;
104 final double doubleFieldAsc;
105 final transient String str;
106
107 public Pojo1(Object... argv) {
108 stringFieldAsc = (String) argv[0];
109 intFieldAsc = (Integer) argv[1];
110 doubleFieldAsc = (Double) argv[2];
111 str = new StringBuilder()
112 .append("{ ")
113 .append(null == stringFieldAsc ? "" : "\"")
114 .append(stringFieldAsc)
115 .append(null == stringFieldAsc ? "" : "\"").append(", ")
116 .append(intFieldAsc).append(", ")
117 .append(doubleFieldAsc)
118 .append(" }")
119 .toString();
120 }
121
122 @Override
123 public String toString() {
124 return str;
125 }
126
127 @Override
128 public int compareTo(Pojo1 o) {
129 int cmp = stringFieldAsc.compareTo(o.stringFieldAsc);
130 if (cmp != 0) {
131 return cmp;
132 }
133 cmp = Integer.valueOf(intFieldAsc).compareTo(Integer.valueOf(o.intFieldAsc));
134 if (cmp != 0) {
135 return cmp;
136 }
137 return Double.compare(doubleFieldAsc, o.doubleFieldAsc);
138 }
139
140 @Override
141 public int hashCode() {
142 final int prime = 31;
143 int result = 1;
144 long temp;
145 temp = Double.doubleToLongBits(doubleFieldAsc);
146 result = prime * result + (int) (temp ^ (temp >>> 32));
147 result = prime * result + intFieldAsc;
148 result = prime * result + ((stringFieldAsc == null) ? 0 : stringFieldAsc.hashCode());
149 return result;
150 }
151
152 @Override
153 public boolean equals(Object obj) {
154 if (this == obj) {
155 return true;
156 }
157 if (obj == null) {
158 return false;
159 }
160 if (getClass() != obj.getClass()) {
161 return false;
162 }
163 Pojo1 other = (Pojo1) obj;
164 if (Double.doubleToLongBits(doubleFieldAsc) != Double.doubleToLongBits(other.doubleFieldAsc)) {
165 return false;
166 }
167 if (intFieldAsc != other.intFieldAsc) {
168 return false;
169 }
170 if (stringFieldAsc == null) {
171 if (other.stringFieldAsc != null) {
172 return false;
173 }
174 } else if (!stringFieldAsc.equals(other.stringFieldAsc)) {
175 return false;
176 }
177 return true;
178 }
179 }
180
181
182
183
184 private static class Pojo2 implements Comparable<Pojo2> {
185 final byte[] byteField1Asc;
186 final byte[] byteField2Dsc;
187 final String stringFieldDsc;
188 final byte[] byteField3Dsc;
189 final transient String str;
190
191 public Pojo2(Object... vals) {
192 byteField1Asc = vals.length > 0 ? (byte[]) vals[0] : null;
193 byteField2Dsc = vals.length > 1 ? (byte[]) vals[1] : null;
194 stringFieldDsc = vals.length > 2 ? (String) vals[2] : null;
195 byteField3Dsc = vals.length > 3 ? (byte[]) vals[3] : null;
196 str = new StringBuilder()
197 .append("{ ")
198 .append(Bytes.toStringBinary(byteField1Asc)).append(", ")
199 .append(Bytes.toStringBinary(byteField2Dsc)).append(", ")
200 .append(null == stringFieldDsc ? "" : "\"")
201 .append(stringFieldDsc)
202 .append(null == stringFieldDsc ? "" : "\"").append(", ")
203 .append(Bytes.toStringBinary(byteField3Dsc))
204 .append(" }")
205 .toString();
206 }
207
208 @Override
209 public String toString() {
210 return str;
211 }
212
213 @Override
214 public int compareTo(Pojo2 o) {
215 int cmp = NULL_SAFE_BYTES_COMPARATOR.compare(byteField1Asc, o.byteField1Asc);
216 if (cmp != 0) {
217 return cmp;
218 }
219 cmp = -NULL_SAFE_BYTES_COMPARATOR.compare(byteField2Dsc, o.byteField2Dsc);
220 if (cmp != 0) {
221 return cmp;
222 }
223 if (null == stringFieldDsc) {
224 cmp = 1;
225 }
226 else if (null == o.stringFieldDsc) {
227 cmp = -1;
228 }
229 else if (stringFieldDsc.equals(o.stringFieldDsc)) {
230 cmp = 0;
231 }
232 else cmp = -stringFieldDsc.compareTo(o.stringFieldDsc);
233 if (cmp != 0) {
234 return cmp;
235 }
236 return -NULL_SAFE_BYTES_COMPARATOR.compare(byteField3Dsc, o.byteField3Dsc);
237 }
238
239 @Override
240 public int hashCode() {
241 final int prime = 31;
242 int result = 1;
243 result = prime * result + Arrays.hashCode(byteField1Asc);
244 result = prime * result + Arrays.hashCode(byteField2Dsc);
245 result = prime * result + Arrays.hashCode(byteField3Dsc);
246 result = prime * result + ((stringFieldDsc == null) ? 0 : stringFieldDsc.hashCode());
247 return result;
248 }
249
250 @Override
251 public boolean equals(Object obj) {
252 if (this == obj) {
253 return true;
254 }
255 if (obj == null) {
256 return false;
257 }
258 if (getClass() != obj.getClass()) {
259 return false;
260 }
261 Pojo2 other = (Pojo2) obj;
262 if (!Arrays.equals(byteField1Asc, other.byteField1Asc)) {
263 return false;
264 }
265 if (!Arrays.equals(byteField2Dsc, other.byteField2Dsc)) {
266 return false;
267 }
268 if (!Arrays.equals(byteField3Dsc, other.byteField3Dsc)) {
269 return false;
270 }
271 if (stringFieldDsc == null) {
272 if (other.stringFieldDsc != null) {
273 return false;
274 }
275 } else if (!stringFieldDsc.equals(other.stringFieldDsc)) {
276 return false;
277 }
278 return true;
279 }
280 }
281
282
283
284
285 private static class SpecializedPojo1Type1 implements DataType<Pojo1> {
286
287 private static final RawStringTerminated stringField = new RawStringTerminated("/");
288 private static final RawInteger intField = new RawInteger();
289 private static final RawDouble doubleField = new RawDouble();
290
291
292
293
294 public static Struct GENERIC =
295 new StructBuilder().add(stringField)
296 .add(intField)
297 .add(doubleField)
298 .toStruct();
299
300 @Override
301 public boolean isOrderPreserving() { return true; }
302
303 @Override
304 public Order getOrder() { return null; }
305
306 @Override
307 public boolean isNullable() { return false; }
308
309 @Override
310 public boolean isSkippable() { return true; }
311
312 @Override
313 public int encodedLength(Pojo1 val) {
314 return
315 stringField.encodedLength(val.stringFieldAsc) +
316 intField.encodedLength(val.intFieldAsc) +
317 doubleField.encodedLength(val.doubleFieldAsc);
318 }
319
320 @Override
321 public Class<Pojo1> encodedClass() { return Pojo1.class; }
322
323 @Override
324 public int skip(PositionedByteRange src) {
325 int skipped = stringField.skip(src);
326 skipped += intField.skip(src);
327 skipped += doubleField.skip(src);
328 return skipped;
329 }
330
331 @Override
332 public Pojo1 decode(PositionedByteRange src) {
333 Object[] ret = new Object[3];
334 ret[0] = stringField.decode(src);
335 ret[1] = intField.decode(src);
336 ret[2] = doubleField.decode(src);
337 return new Pojo1(ret);
338 }
339
340 @Override
341 public int encode(PositionedByteRange dst, Pojo1 val) {
342 int written = stringField.encode(dst, val.stringFieldAsc);
343 written += intField.encode(dst, val.intFieldAsc);
344 written += doubleField.encode(dst, val.doubleFieldAsc);
345 return written;
346 }
347 }
348
349
350
351
352 private static class SpecializedPojo2Type1 implements DataType<Pojo2> {
353
354 private static RawBytesTerminated byteField1 = new RawBytesTerminated("/");
355 private static RawBytesTerminated byteField2 =
356 new RawBytesTerminated(Order.DESCENDING, "/");
357 private static RawStringTerminated stringField =
358 new RawStringTerminated(Order.DESCENDING, new byte[] { 0x00 });
359 private static RawBytes byteField3 = RawBytes.DESCENDING;
360
361
362
363
364 public static Struct GENERIC =
365 new StructBuilder().add(byteField1)
366 .add(byteField2)
367 .add(stringField)
368 .add(byteField3)
369 .toStruct();
370
371 @Override
372 public boolean isOrderPreserving() { return true; }
373
374 @Override
375 public Order getOrder() { return null; }
376
377 @Override
378 public boolean isNullable() { return false; }
379
380 @Override
381 public boolean isSkippable() { return true; }
382
383 @Override
384 public int encodedLength(Pojo2 val) {
385 return
386 byteField1.encodedLength(val.byteField1Asc) +
387 byteField2.encodedLength(val.byteField2Dsc) +
388 stringField.encodedLength(val.stringFieldDsc) +
389 byteField3.encodedLength(val.byteField3Dsc);
390 }
391
392 @Override
393 public Class<Pojo2> encodedClass() { return Pojo2.class; }
394
395 @Override
396 public int skip(PositionedByteRange src) {
397 int skipped = byteField1.skip(src);
398 skipped += byteField2.skip(src);
399 skipped += stringField.skip(src);
400 skipped += byteField3.skip(src);
401 return skipped;
402 }
403
404 @Override
405 public Pojo2 decode(PositionedByteRange src) {
406 Object[] ret = new Object[4];
407 ret[0] = byteField1.decode(src);
408 ret[1] = byteField2.decode(src);
409 ret[2] = stringField.decode(src);
410 ret[3] = byteField3.decode(src);
411 return new Pojo2(ret);
412 }
413
414 @Override
415 public int encode(PositionedByteRange dst, Pojo2 val) {
416 int written = byteField1.encode(dst, val.byteField1Asc);
417 written += byteField2.encode(dst, val.byteField2Dsc);
418 written += stringField.encode(dst, val.stringFieldDsc);
419 written += byteField3.encode(dst, val.byteField3Dsc);
420 return written;
421 }
422 }
423
424 @Test
425 @SuppressWarnings("unchecked")
426 public void testOrderPreservation() throws Exception {
427 Object[] vals = new Object[constructorArgs.length];
428 PositionedByteRange[] encodedGeneric = new PositionedByteRange[constructorArgs.length];
429 PositionedByteRange[] encodedSpecialized = new PositionedByteRange[constructorArgs.length];
430 Constructor<?> ctor = specialized.encodedClass().getConstructor(Object[].class);
431 for (int i = 0; i < vals.length; i++) {
432 vals[i] = ctor.newInstance(new Object[] { constructorArgs[i] });
433 encodedGeneric[i] = new SimplePositionedMutableByteRange(
434 generic.encodedLength(constructorArgs[i]));
435 encodedSpecialized[i] = new SimplePositionedMutableByteRange(
436 specialized.encodedLength(vals[i]));
437 }
438
439
440 for (int i = 0; i < vals.length; i++) {
441 generic.encode(encodedGeneric[i], constructorArgs[i]);
442 encodedGeneric[i].setPosition(0);
443 specialized.encode(encodedSpecialized[i], vals[i]);
444 encodedSpecialized[i].setPosition(0);
445 assertArrayEquals(encodedGeneric[i].getBytes(), encodedSpecialized[i].getBytes());
446 }
447
448 Arrays.sort(vals);
449 Arrays.sort(encodedGeneric);
450 Arrays.sort(encodedSpecialized);
451
452 for (int i = 0; i < vals.length; i++) {
453 assertEquals(
454 "Struct encoder does not preserve sort order at position " + i,
455 vals[i],
456 ctor.newInstance(new Object[] { generic.decode(encodedGeneric[i]) }));
457 assertEquals(
458 "Specialized encoder does not preserve sort order at position " + i,
459 vals[i], specialized.decode(encodedSpecialized[i]));
460 }
461 }
462 }