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