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.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.SimplePositionedByteRange;
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], "it".getBytes(), "was", "the".getBytes() },
74 new Object[] { "best".getBytes(), new byte[0], "of", "times,".getBytes() },
75 new Object[] { "it".getBytes(), "was".getBytes(), "", "the".getBytes() },
76 new Object[] { "worst".getBytes(), "of".getBytes(), "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) return cmp;
131 cmp = Integer.valueOf(intFieldAsc).compareTo(Integer.valueOf(o.intFieldAsc));
132 if (cmp != 0) return cmp;
133 return Double.compare(doubleFieldAsc, o.doubleFieldAsc);
134 }
135
136 @Override
137 public boolean equals(Object o) {
138 if (this == o) return true;
139 if (null == o) return false;
140 if (!(o instanceof Pojo1)) return false;
141 Pojo1 that = (Pojo1) o;
142 return 0 == this.compareTo(that);
143 }
144 }
145
146
147
148
149 private static class Pojo2 implements Comparable<Pojo2> {
150 final byte[] byteField1Asc;
151 final byte[] byteField2Dsc;
152 final String stringFieldDsc;
153 final byte[] byteField3Dsc;
154 final transient String str;
155
156 public Pojo2(Object... vals) {
157 byte[] empty = new byte[0];
158 byteField1Asc = vals.length > 0 ? (byte[]) vals[0] : empty;
159 byteField2Dsc = vals.length > 1 ? (byte[]) vals[1] : empty;
160 stringFieldDsc = vals.length > 2 ? (String) vals[2] : "";
161 byteField3Dsc = vals.length > 3 ? (byte[]) vals[3] : empty;
162 str = new StringBuilder()
163 .append("{ ")
164 .append(Bytes.toStringBinary(byteField1Asc)).append(", ")
165 .append(Bytes.toStringBinary(byteField2Dsc)).append(", ")
166 .append(null == stringFieldDsc ? "" : "\"")
167 .append(stringFieldDsc)
168 .append(null == stringFieldDsc ? "" : "\"").append(", ")
169 .append(Bytes.toStringBinary(byteField3Dsc))
170 .append(" }")
171 .toString();
172 }
173
174 @Override
175 public String toString() {
176 return str;
177 }
178
179 @Override
180 public int compareTo(Pojo2 o) {
181 int cmp = NULL_SAFE_BYTES_COMPARATOR.compare(byteField1Asc, o.byteField1Asc);
182 if (cmp != 0) return cmp;
183 cmp = -NULL_SAFE_BYTES_COMPARATOR.compare(byteField2Dsc, o.byteField2Dsc);
184 if (cmp != 0) return cmp;
185 if (stringFieldDsc == o.stringFieldDsc) cmp = 0;
186 else if (null == stringFieldDsc) cmp = 1;
187 else if (null == o.stringFieldDsc) cmp = -1;
188 else cmp = -stringFieldDsc.compareTo(o.stringFieldDsc);
189 if (cmp != 0) return cmp;
190 return -NULL_SAFE_BYTES_COMPARATOR.compare(byteField3Dsc, o.byteField3Dsc);
191 }
192
193 @Override
194 public boolean equals(Object o) {
195 if (this == o) return true;
196 if (null == o) return false;
197 if (!(o instanceof Pojo2)) return false;
198 Pojo2 that = (Pojo2) o;
199 return 0 == this.compareTo(that);
200 }
201 }
202
203
204
205
206 private static class SpecializedPojo1Type1 implements DataType<Pojo1> {
207
208 private static final RawStringTerminated stringField = new RawStringTerminated("/");
209 private static final RawInteger intField = new RawInteger();
210 private static final RawDouble doubleField = new RawDouble();
211
212
213
214
215 public static Struct GENERIC =
216 new StructBuilder().add(stringField)
217 .add(intField)
218 .add(doubleField)
219 .toStruct();
220
221 @Override
222 public boolean isOrderPreserving() { return true; }
223
224 @Override
225 public Order getOrder() { return null; }
226
227 @Override
228 public boolean isNullable() { return false; }
229
230 @Override
231 public boolean isSkippable() { return true; }
232
233 @Override
234 public int encodedLength(Pojo1 val) {
235 return
236 stringField.encodedLength(val.stringFieldAsc) +
237 intField.encodedLength(val.intFieldAsc) +
238 doubleField.encodedLength(val.doubleFieldAsc);
239 }
240
241 @Override
242 public Class<Pojo1> encodedClass() { return Pojo1.class; }
243
244 @Override
245 public int skip(PositionedByteRange src) {
246 int skipped = stringField.skip(src);
247 skipped += intField.skip(src);
248 skipped += doubleField.skip(src);
249 return skipped;
250 }
251
252 @Override
253 public Pojo1 decode(PositionedByteRange src) {
254 Object[] ret = new Object[3];
255 ret[0] = stringField.decode(src);
256 ret[1] = intField.decode(src);
257 ret[2] = doubleField.decode(src);
258 return new Pojo1(ret);
259 }
260
261 @Override
262 public int encode(PositionedByteRange dst, Pojo1 val) {
263 int written = stringField.encode(dst, val.stringFieldAsc);
264 written += intField.encode(dst, val.intFieldAsc);
265 written += doubleField.encode(dst, val.doubleFieldAsc);
266 return written;
267 }
268 }
269
270
271
272
273 private static class SpecializedPojo2Type1 implements DataType<Pojo2> {
274
275 private static RawBytesTerminated byteField1 = new RawBytesTerminated("/");
276 private static RawBytesTerminated byteField2 =
277 new RawBytesTerminated(Order.DESCENDING, "/");
278 private static RawStringTerminated stringField =
279 new RawStringTerminated(Order.DESCENDING, new byte[] { 0x00 });
280 private static RawBytes byteField3 = RawBytes.DESCENDING;
281
282
283
284
285 public static Struct GENERIC =
286 new StructBuilder().add(byteField1)
287 .add(byteField2)
288 .add(stringField)
289 .add(byteField3)
290 .toStruct();
291
292 @Override
293 public boolean isOrderPreserving() { return true; }
294
295 @Override
296 public Order getOrder() { return null; }
297
298 @Override
299 public boolean isNullable() { return false; }
300
301 @Override
302 public boolean isSkippable() { return true; }
303
304 @Override
305 public int encodedLength(Pojo2 val) {
306 return
307 byteField1.encodedLength(val.byteField1Asc) +
308 byteField2.encodedLength(val.byteField2Dsc) +
309 stringField.encodedLength(val.stringFieldDsc) +
310 byteField3.encodedLength(val.byteField3Dsc);
311 }
312
313 @Override
314 public Class<Pojo2> encodedClass() { return Pojo2.class; }
315
316 @Override
317 public int skip(PositionedByteRange src) {
318 int skipped = byteField1.skip(src);
319 skipped += byteField2.skip(src);
320 skipped += stringField.skip(src);
321 skipped += byteField3.skip(src);
322 return skipped;
323 }
324
325 @Override
326 public Pojo2 decode(PositionedByteRange src) {
327 Object[] ret = new Object[4];
328 ret[0] = byteField1.decode(src);
329 ret[1] = byteField2.decode(src);
330 ret[2] = stringField.decode(src);
331 ret[3] = byteField3.decode(src);
332 return new Pojo2(ret);
333 }
334
335 @Override
336 public int encode(PositionedByteRange dst, Pojo2 val) {
337 int written = byteField1.encode(dst, val.byteField1Asc);
338 written += byteField2.encode(dst, val.byteField2Dsc);
339 written += stringField.encode(dst, val.stringFieldDsc);
340 written += byteField3.encode(dst, val.byteField3Dsc);
341 return written;
342 }
343 }
344
345 @Test
346 @SuppressWarnings("unchecked")
347 public void testOrderPreservation() throws Exception {
348 Object[] vals = new Object[constructorArgs.length];
349 PositionedByteRange[] encodedGeneric = new PositionedByteRange[constructorArgs.length];
350 PositionedByteRange[] encodedSpecialized = new PositionedByteRange[constructorArgs.length];
351 Constructor<?> ctor = specialized.encodedClass().getConstructor(Object[].class);
352 for (int i = 0; i < vals.length; i++) {
353 vals[i] = ctor.newInstance(new Object[] { constructorArgs[i] });
354 encodedGeneric[i] = new SimplePositionedByteRange(generic.encodedLength(constructorArgs[i]));
355 encodedSpecialized[i] = new SimplePositionedByteRange(specialized.encodedLength(vals[i]));
356 }
357
358
359 for (int i = 0; i < vals.length; i++) {
360 generic.encode(encodedGeneric[i], constructorArgs[i]);
361 encodedGeneric[i].setPosition(0);
362 specialized.encode(encodedSpecialized[i], vals[i]);
363 encodedSpecialized[i].setPosition(0);
364 assertArrayEquals(encodedGeneric[i].getBytes(), encodedSpecialized[i].getBytes());
365 }
366
367 Arrays.sort(vals);
368 Arrays.sort(encodedGeneric);
369 Arrays.sort(encodedSpecialized);
370
371 for (int i = 0; i < vals.length; i++) {
372 assertEquals(
373 "Struct encoder does not preserve sort order at position " + i,
374 vals[i],
375 ctor.newInstance(new Object[] { generic.decode(encodedGeneric[i]) }));
376 assertEquals(
377 "Specialized encoder does not preserve sort order at position " + i,
378 vals[i], specialized.decode(encodedSpecialized[i]));
379 }
380 }
381 }