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