001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.types; 019 020import static org.junit.Assert.assertArrayEquals; 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.assertNull; 023 024import java.math.BigDecimal; 025import java.util.Arrays; 026import org.apache.hadoop.hbase.HBaseClassTestRule; 027import org.apache.hadoop.hbase.testclassification.MiscTests; 028import org.apache.hadoop.hbase.testclassification.SmallTests; 029import org.apache.hadoop.hbase.util.Order; 030import org.apache.hadoop.hbase.util.PositionedByteRange; 031import org.apache.hadoop.hbase.util.SimplePositionedMutableByteRange; 032import org.junit.ClassRule; 033import org.junit.Test; 034import org.junit.experimental.categories.Category; 035 036@Category({ MiscTests.class, SmallTests.class }) 037public class TestStructNullExtension { 038 039 @ClassRule 040 public static final HBaseClassTestRule CLASS_RULE = 041 HBaseClassTestRule.forClass(TestStructNullExtension.class); 042 043 /** 044 * Verify null extension respects the type's isNullable field. 045 */ 046 @Test(expected = NullPointerException.class) 047 public void testNonNullableNullExtension() { 048 Struct s = new StructBuilder().add(new RawStringTerminated("|")) // not nullable 049 .toStruct(); 050 PositionedByteRange buf = new SimplePositionedMutableByteRange(4); 051 s.encode(buf, new Object[1]); 052 } 053 054 /** 055 * Positive cases for null extension. 056 */ 057 @Test 058 public void testNullableNullExtension() { 059 // the following field members are used because they're all nullable 060 final StructBuilder builder = new StructBuilder().add(new OrderedNumeric(Order.ASCENDING)) 061 .add(new OrderedString(Order.ASCENDING)); 062 Struct shorter = builder.toStruct(); 063 final Struct longer = builder 064 // intentionally include a wrapped instance to test wrapper behavior. 065 .add(new TerminatedWrapper<>(new OrderedString(Order.ASCENDING), "/")) 066 .add(new OrderedNumeric(Order.ASCENDING)).toStruct(); 067 068 PositionedByteRange buf1 = new SimplePositionedMutableByteRange(7); 069 Object[] val1 = new Object[] { BigDecimal.ONE, "foo" }; // => 2 bytes + 5 bytes 070 assertEquals("Encoding shorter value wrote a surprising number of bytes.", buf1.getLength(), 071 shorter.encode(buf1, val1)); 072 int shortLen = buf1.getLength(); 073 074 // test iterator 075 buf1.setPosition(0); 076 StructIterator it = longer.iterator(buf1); 077 it.skip(); 078 it.skip(); 079 assertEquals("Position should be at end. Broken test.", buf1.getLength(), buf1.getPosition()); 080 assertEquals("Failed to skip null element with extended struct.", 0, it.skip()); 081 assertEquals("Failed to skip null element with extended struct.", 0, it.skip()); 082 083 buf1.setPosition(0); 084 it = longer.iterator(buf1); 085 assertEquals(BigDecimal.ONE, it.next()); 086 assertEquals("foo", it.next()); 087 assertEquals("Position should be at end. Broken test.", buf1.getLength(), buf1.getPosition()); 088 assertNull("Failed to skip null element with extended struct.", it.next()); 089 assertNull("Failed to skip null element with extended struct.", it.next()); 090 091 // test Struct 092 buf1.setPosition(0); 093 assertArrayEquals("Simple struct decoding is broken.", val1, shorter.decode(buf1)); 094 095 buf1.setPosition(0); 096 assertArrayEquals("Decoding short value with extended struct should append null elements.", 097 Arrays.copyOf(val1, 4), longer.decode(buf1)); 098 099 // test omission of trailing members 100 PositionedByteRange buf2 = new SimplePositionedMutableByteRange(7); 101 buf1.setPosition(0); 102 assertEquals( 103 "Encoding a short value with extended struct should have same result as using short struct.", 104 shortLen, longer.encode(buf2, val1)); 105 assertArrayEquals( 106 "Encoding a short value with extended struct should have same result as using short struct", 107 buf1.getBytes(), buf2.getBytes()); 108 109 // test null trailing members 110 // all fields are nullable, so nothing should hit the buffer. 111 val1 = new Object[] { null, null, null, null }; // => 0 bytes 112 buf1.set(0); 113 buf2.set(0); 114 assertEquals("Encoding null-truncated value wrote a surprising number of bytes.", 115 buf1.getLength(), longer.encode(buf1, new Object[0])); 116 assertEquals("Encoding null-extended value wrote a surprising number of bytes.", 117 buf1.getLength(), longer.encode(buf1, val1)); 118 assertArrayEquals("Encoded unexpected result.", buf1.getBytes(), buf2.getBytes()); 119 assertArrayEquals("Decoded unexpected result.", val1, longer.decode(buf2)); 120 121 // all fields are nullable, so only 1 should hit the buffer. 122 Object[] val2 = new Object[] { BigDecimal.ONE, null, null, null }; // => 2 bytes 123 buf1.set(2); 124 buf2.set(2); 125 assertEquals("Encoding null-truncated value wrote a surprising number of bytes.", 126 buf1.getLength(), longer.encode(buf1, Arrays.copyOf(val2, 1))); 127 assertEquals("Encoding null-extended value wrote a surprising number of bytes.", 128 buf2.getLength(), longer.encode(buf2, val2)); 129 assertArrayEquals("Encoded unexpected result.", buf1.getBytes(), buf2.getBytes()); 130 buf2.setPosition(0); 131 assertArrayEquals("Decoded unexpected result.", val2, longer.decode(buf2)); 132 133 // all fields are nullable, so only 1, null, "foo" should hit the buffer. 134 // => 2 bytes + 1 byte + 6 bytes 135 Object[] val3 = new Object[] { BigDecimal.ONE, null, "foo", null }; 136 buf1.set(9); 137 buf2.set(9); 138 assertEquals("Encoding null-truncated value wrote a surprising number of bytes.", 139 buf1.getLength(), longer.encode(buf1, Arrays.copyOf(val3, 3))); 140 assertEquals("Encoding null-extended value wrote a surprising number of bytes.", 141 buf2.getLength(), longer.encode(buf2, val3)); 142 assertArrayEquals("Encoded unexpected result.", buf1.getBytes(), buf2.getBytes()); 143 buf2.setPosition(0); 144 assertArrayEquals("Decoded unexpected result.", val3, longer.decode(buf2)); 145 } 146}