View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.types;
19  
20  import static org.junit.Assert.assertArrayEquals;
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertNull;
23  
24  import java.math.BigDecimal;
25  import java.util.Arrays;
26  
27  import org.apache.hadoop.hbase.SmallTests;
28  import org.apache.hadoop.hbase.util.PositionedByteRange;
29  import org.apache.hadoop.hbase.util.SimplePositionedByteRange;
30  import org.junit.Test;
31  import org.junit.experimental.categories.Category;
32  
33  @Category(SmallTests.class)
34  public class TestStructNullExtension {
35  
36    /**
37     * Verify null extension respects the type's isNullable field.
38     */
39    @Test(expected = NullPointerException.class)
40    public void testNonNullableNullExtension() {
41      Struct s = new StructBuilder()
42          .add(new RawStringTerminated("|")) // not nullable
43          .toStruct();
44      PositionedByteRange buf = new SimplePositionedByteRange(4);
45      s.encode(buf, new Object[1]);
46    }
47  
48    /**
49     * Positive cases for null extension.
50     */
51    @Test
52    public void testNullableNullExtension() {
53      // the following field members are used because they're all nullable
54      StructBuilder builder = new StructBuilder()
55          .add(OrderedNumeric.ASCENDING)
56          .add(OrderedString.ASCENDING);
57      Struct shorter = builder.toStruct();
58      Struct longer = builder
59          // intentionally include a wrapped instance to test wrapper behavior.
60          .add(new TerminatedWrapper<String>(OrderedString.ASCENDING, "/"))
61          .add(OrderedNumeric.ASCENDING)
62          .toStruct();
63  
64      PositionedByteRange buf1 = new SimplePositionedByteRange(7);
65      Object[] val1 = new Object[] { BigDecimal.ONE, "foo" }; // => 2 bytes + 5 bytes
66      assertEquals("Encoding shorter value wrote a surprising number of bytes.",
67        buf1.getLength(), shorter.encode(buf1, val1));
68      int shortLen = buf1.getLength();
69  
70      // test iterator
71      buf1.setPosition(0);
72      StructIterator it = longer.iterator(buf1);
73      it.skip();
74      it.skip();
75      assertEquals("Position should be at end. Broken test.", buf1.getLength(), buf1.getPosition());
76      assertEquals("Failed to skip null element with extended struct.", 0, it.skip());
77      assertEquals("Failed to skip null element with extended struct.", 0, it.skip());
78  
79      buf1.setPosition(0);
80      it = longer.iterator(buf1);
81      assertEquals(BigDecimal.ONE, it.next());
82      assertEquals("foo", it.next());
83      assertEquals("Position should be at end. Broken test.", buf1.getLength(), buf1.getPosition());
84      assertNull("Failed to skip null element with extended struct.", it.next());
85      assertNull("Failed to skip null element with extended struct.", it.next());
86  
87      // test Struct
88      buf1.setPosition(0);
89      assertArrayEquals("Simple struct decoding is broken.", val1, shorter.decode(buf1));
90  
91      buf1.setPosition(0);
92      assertArrayEquals("Decoding short value with extended struct should append null elements.",
93        Arrays.copyOf(val1, 4), longer.decode(buf1));
94  
95      // test omission of trailing members
96      PositionedByteRange buf2 = new SimplePositionedByteRange(7);
97      buf1.setPosition(0);
98      assertEquals(
99        "Encoding a short value with extended struct should have same result as using short struct.",
100       shortLen, longer.encode(buf2, val1));
101     assertArrayEquals(
102       "Encoding a short value with extended struct should have same result as using short struct",
103       buf1.getBytes(), buf2.getBytes());
104 
105     // test null trailing members
106     // all fields are nullable, so nothing should hit the buffer.
107     val1 = new Object[] { null, null, null, null }; // => 0 bytes
108     buf1.set(0);
109     buf2.set(0);
110     assertEquals("Encoding null-truncated value wrote a surprising number of bytes.",
111       buf1.getLength(), longer.encode(buf1, new Object[0]));
112     assertEquals("Encoding null-extended value wrote a surprising number of bytes.",
113       buf1.getLength(), longer.encode(buf1, val1));
114     assertArrayEquals("Encoded unexpected result.", buf1.getBytes(), buf2.getBytes());
115     assertArrayEquals("Decoded unexpected result.", val1, longer.decode(buf2));
116 
117     // all fields are nullable, so only 1 should hit the buffer.
118     Object[] val2 = new Object[] { BigDecimal.ONE, null, null, null }; // => 2 bytes
119     buf1.set(2);
120     buf2.set(2);
121     assertEquals("Encoding null-truncated value wrote a surprising number of bytes.",
122       buf1.getLength(), longer.encode(buf1, Arrays.copyOf(val2, 1)));
123     assertEquals("Encoding null-extended value wrote a surprising number of bytes.",
124       buf2.getLength(), longer.encode(buf2, val2));
125     assertArrayEquals("Encoded unexpected result.", buf1.getBytes(), buf2.getBytes());
126     buf2.setPosition(0);
127     assertArrayEquals("Decoded unexpected result.", val2, longer.decode(buf2));
128 
129     // all fields are nullable, so only 1, null, "foo" should hit the buffer.
130     // => 2 bytes + 1 byte + 6 bytes
131     Object[] val3 = new Object[] { BigDecimal.ONE, null, "foo", null };
132     buf1.set(9);
133     buf2.set(9);
134     assertEquals("Encoding null-truncated value wrote a surprising number of bytes.",
135       buf1.getLength(), longer.encode(buf1, Arrays.copyOf(val3, 3)));
136     assertEquals("Encoding null-extended value wrote a surprising number of bytes.",
137       buf2.getLength(), longer.encode(buf2, val3));
138     assertArrayEquals("Encoded unexpected result.", buf1.getBytes(), buf2.getBytes());
139     buf2.setPosition(0);
140     assertArrayEquals("Decoded unexpected result.", val3, longer.decode(buf2));
141   }
142 }