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().add(new RawStringTerminated("|")) // not nullable 048 .toStruct(); 049 PositionedByteRange buf = new SimplePositionedMutableByteRange(4); 050 s.encode(buf, new Object[1]); 051 } 052 053 /** 054 * Positive cases for null extension. 055 */ 056 @Test 057 public void testNullableNullExtension() { 058 // the following field members are used because they're all nullable 059 StructBuilder builder = 060 new StructBuilder().add(OrderedNumeric.ASCENDING).add(OrderedString.ASCENDING); 061 Struct shorter = builder.toStruct(); 062 Struct longer = builder 063 // intentionally include a wrapped instance to test wrapper behavior. 064 .add(new TerminatedWrapper<>(OrderedString.ASCENDING, "/")).add(OrderedNumeric.ASCENDING) 065 .toStruct(); 066 067 PositionedByteRange buf1 = new SimplePositionedMutableByteRange(7); 068 Object[] val1 = new Object[] { BigDecimal.ONE, "foo" }; // => 2 bytes + 5 bytes 069 assertEquals("Encoding shorter value wrote a surprising number of bytes.", buf1.getLength(), 070 shorter.encode(buf1, val1)); 071 int shortLen = buf1.getLength(); 072 073 // test iterator 074 buf1.setPosition(0); 075 StructIterator it = longer.iterator(buf1); 076 it.skip(); 077 it.skip(); 078 assertEquals("Position should be at end. Broken test.", buf1.getLength(), buf1.getPosition()); 079 assertEquals("Failed to skip null element with extended struct.", 0, it.skip()); 080 assertEquals("Failed to skip null element with extended struct.", 0, it.skip()); 081 082 buf1.setPosition(0); 083 it = longer.iterator(buf1); 084 assertEquals(BigDecimal.ONE, it.next()); 085 assertEquals("foo", it.next()); 086 assertEquals("Position should be at end. Broken test.", buf1.getLength(), buf1.getPosition()); 087 assertNull("Failed to skip null element with extended struct.", it.next()); 088 assertNull("Failed to skip null element with extended struct.", it.next()); 089 090 // test Struct 091 buf1.setPosition(0); 092 assertArrayEquals("Simple struct decoding is broken.", val1, shorter.decode(buf1)); 093 094 buf1.setPosition(0); 095 assertArrayEquals("Decoding short value with extended struct should append null elements.", 096 Arrays.copyOf(val1, 4), longer.decode(buf1)); 097 098 // test omission of trailing members 099 PositionedByteRange buf2 = new SimplePositionedMutableByteRange(7); 100 buf1.setPosition(0); 101 assertEquals( 102 "Encoding a short value with extended struct should have same result as using short struct.", 103 shortLen, longer.encode(buf2, val1)); 104 assertArrayEquals( 105 "Encoding a short value with extended struct should have same result as using short struct", 106 buf1.getBytes(), buf2.getBytes()); 107 108 // test null trailing members 109 // all fields are nullable, so nothing should hit the buffer. 110 val1 = new Object[] { null, null, null, null }; // => 0 bytes 111 buf1.set(0); 112 buf2.set(0); 113 assertEquals("Encoding null-truncated value wrote a surprising number of bytes.", 114 buf1.getLength(), longer.encode(buf1, new Object[0])); 115 assertEquals("Encoding null-extended value wrote a surprising number of bytes.", 116 buf1.getLength(), longer.encode(buf1, val1)); 117 assertArrayEquals("Encoded unexpected result.", buf1.getBytes(), buf2.getBytes()); 118 assertArrayEquals("Decoded unexpected result.", val1, longer.decode(buf2)); 119 120 // all fields are nullable, so only 1 should hit the buffer. 121 Object[] val2 = new Object[] { BigDecimal.ONE, null, null, null }; // => 2 bytes 122 buf1.set(2); 123 buf2.set(2); 124 assertEquals("Encoding null-truncated value wrote a surprising number of bytes.", 125 buf1.getLength(), longer.encode(buf1, Arrays.copyOf(val2, 1))); 126 assertEquals("Encoding null-extended value wrote a surprising number of bytes.", 127 buf2.getLength(), longer.encode(buf2, val2)); 128 assertArrayEquals("Encoded unexpected result.", buf1.getBytes(), buf2.getBytes()); 129 buf2.setPosition(0); 130 assertArrayEquals("Decoded unexpected result.", val2, longer.decode(buf2)); 131 132 // all fields are nullable, so only 1, null, "foo" should hit the buffer. 133 // => 2 bytes + 1 byte + 6 bytes 134 Object[] val3 = new Object[] { BigDecimal.ONE, null, "foo", null }; 135 buf1.set(9); 136 buf2.set(9); 137 assertEquals("Encoding null-truncated value wrote a surprising number of bytes.", 138 buf1.getLength(), longer.encode(buf1, Arrays.copyOf(val3, 3))); 139 assertEquals("Encoding null-extended value wrote a surprising number of bytes.", 140 buf2.getLength(), longer.encode(buf2, val3)); 141 assertArrayEquals("Encoded unexpected result.", buf1.getBytes(), buf2.getBytes()); 142 buf2.setPosition(0); 143 assertArrayEquals("Decoded unexpected result.", val3, longer.decode(buf2)); 144 } 145}