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.PositionedByteRange; 030import org.apache.hadoop.hbase.util.SimplePositionedMutableByteRange; 031import org.junit.ClassRule; 032import org.junit.Test; 033import org.junit.experimental.categories.Category; 034 035@Category({MiscTests.class, SmallTests.class}) 036public class TestStructNullExtension { 037 038 @ClassRule 039 public static final HBaseClassTestRule CLASS_RULE = 040 HBaseClassTestRule.forClass(TestStructNullExtension.class); 041 042 /** 043 * Verify null extension respects the type's isNullable field. 044 */ 045 @Test(expected = NullPointerException.class) 046 public void testNonNullableNullExtension() { 047 Struct s = new StructBuilder() 048 .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 StructBuilder builder = new StructBuilder() 061 .add(OrderedNumeric.ASCENDING) 062 .add(OrderedString.ASCENDING); 063 Struct shorter = builder.toStruct(); 064 Struct longer = builder 065 // intentionally include a wrapped instance to test wrapper behavior. 066 .add(new TerminatedWrapper<>(OrderedString.ASCENDING, "/")) 067 .add(OrderedNumeric.ASCENDING) 068 .toStruct(); 069 070 PositionedByteRange buf1 = new SimplePositionedMutableByteRange(7); 071 Object[] val1 = new Object[] { BigDecimal.ONE, "foo" }; // => 2 bytes + 5 bytes 072 assertEquals("Encoding shorter value wrote a surprising number of bytes.", 073 buf1.getLength(), shorter.encode(buf1, val1)); 074 int shortLen = buf1.getLength(); 075 076 // test iterator 077 buf1.setPosition(0); 078 StructIterator it = longer.iterator(buf1); 079 it.skip(); 080 it.skip(); 081 assertEquals("Position should be at end. Broken test.", buf1.getLength(), buf1.getPosition()); 082 assertEquals("Failed to skip null element with extended struct.", 0, it.skip()); 083 assertEquals("Failed to skip null element with extended struct.", 0, it.skip()); 084 085 buf1.setPosition(0); 086 it = longer.iterator(buf1); 087 assertEquals(BigDecimal.ONE, it.next()); 088 assertEquals("foo", it.next()); 089 assertEquals("Position should be at end. Broken test.", buf1.getLength(), buf1.getPosition()); 090 assertNull("Failed to skip null element with extended struct.", it.next()); 091 assertNull("Failed to skip null element with extended struct.", it.next()); 092 093 // test Struct 094 buf1.setPosition(0); 095 assertArrayEquals("Simple struct decoding is broken.", val1, shorter.decode(buf1)); 096 097 buf1.setPosition(0); 098 assertArrayEquals("Decoding short value with extended struct should append null elements.", 099 Arrays.copyOf(val1, 4), longer.decode(buf1)); 100 101 // test omission of trailing members 102 PositionedByteRange buf2 = new SimplePositionedMutableByteRange(7); 103 buf1.setPosition(0); 104 assertEquals( 105 "Encoding a short value with extended struct should have same result as using short struct.", 106 shortLen, longer.encode(buf2, val1)); 107 assertArrayEquals( 108 "Encoding a short value with extended struct should have same result as using short struct", 109 buf1.getBytes(), buf2.getBytes()); 110 111 // test null trailing members 112 // all fields are nullable, so nothing should hit the buffer. 113 val1 = new Object[] { null, null, null, null }; // => 0 bytes 114 buf1.set(0); 115 buf2.set(0); 116 assertEquals("Encoding null-truncated value wrote a surprising number of bytes.", 117 buf1.getLength(), longer.encode(buf1, new Object[0])); 118 assertEquals("Encoding null-extended value wrote a surprising number of bytes.", 119 buf1.getLength(), longer.encode(buf1, val1)); 120 assertArrayEquals("Encoded unexpected result.", buf1.getBytes(), buf2.getBytes()); 121 assertArrayEquals("Decoded unexpected result.", val1, longer.decode(buf2)); 122 123 // all fields are nullable, so only 1 should hit the buffer. 124 Object[] val2 = new Object[] { BigDecimal.ONE, null, null, null }; // => 2 bytes 125 buf1.set(2); 126 buf2.set(2); 127 assertEquals("Encoding null-truncated value wrote a surprising number of bytes.", 128 buf1.getLength(), longer.encode(buf1, Arrays.copyOf(val2, 1))); 129 assertEquals("Encoding null-extended value wrote a surprising number of bytes.", 130 buf2.getLength(), longer.encode(buf2, val2)); 131 assertArrayEquals("Encoded unexpected result.", buf1.getBytes(), buf2.getBytes()); 132 buf2.setPosition(0); 133 assertArrayEquals("Decoded unexpected result.", val2, longer.decode(buf2)); 134 135 // all fields are nullable, so only 1, null, "foo" should hit the buffer. 136 // => 2 bytes + 1 byte + 6 bytes 137 Object[] val3 = new Object[] { BigDecimal.ONE, null, "foo", null }; 138 buf1.set(9); 139 buf2.set(9); 140 assertEquals("Encoding null-truncated value wrote a surprising number of bytes.", 141 buf1.getLength(), longer.encode(buf1, Arrays.copyOf(val3, 3))); 142 assertEquals("Encoding null-extended value wrote a surprising number of bytes.", 143 buf2.getLength(), longer.encode(buf2, val3)); 144 assertArrayEquals("Encoded unexpected result.", buf1.getBytes(), buf2.getBytes()); 145 buf2.setPosition(0); 146 assertArrayEquals("Decoded unexpected result.", val3, longer.decode(buf2)); 147 } 148}