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.jupiter.api.Assertions.assertArrayEquals; 021import static org.junit.jupiter.api.Assertions.assertEquals; 022import static org.junit.jupiter.api.Assertions.assertNull; 023import static org.junit.jupiter.api.Assertions.assertThrows; 024 025import java.math.BigDecimal; 026import java.util.Arrays; 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.jupiter.api.Tag; 033import org.junit.jupiter.api.Test; 034 035@Tag(MiscTests.TAG) 036@Tag(SmallTests.TAG) 037public class TestStructNullExtension { 038 039 /** 040 * Verify null extension respects the type's isNullable field. 041 */ 042 @Test 043 public void testNonNullableNullExtension() { 044 // not nullable 045 Struct s = new StructBuilder().add(new RawStringTerminated("|")).toStruct(); 046 PositionedByteRange buf = new SimplePositionedMutableByteRange(4); 047 assertThrows(NullPointerException.class, () -> { 048 s.encode(buf, new Object[1]); 049 }); 050 } 051 052 /** 053 * Positive cases for null extension. 054 */ 055 @Test 056 public void testNullableNullExtension() { 057 // the following field members are used because they're all nullable 058 final StructBuilder builder = new StructBuilder().add(new OrderedNumeric(Order.ASCENDING)) 059 .add(new OrderedString(Order.ASCENDING)); 060 Struct shorter = builder.toStruct(); 061 final Struct longer = builder 062 // intentionally include a wrapped instance to test wrapper behavior. 063 .add(new TerminatedWrapper<>(new OrderedString(Order.ASCENDING), "/")) 064 .add(new OrderedNumeric(Order.ASCENDING)).toStruct(); 065 066 PositionedByteRange buf1 = new SimplePositionedMutableByteRange(7); 067 Object[] val1 = new Object[] { BigDecimal.ONE, "foo" }; // => 2 bytes + 5 bytes 068 assertEquals(buf1.getLength(), shorter.encode(buf1, val1), 069 "Encoding shorter value wrote a surprising number of bytes."); 070 int shortLen = buf1.getLength(); 071 072 // test iterator 073 buf1.setPosition(0); 074 StructIterator it = longer.iterator(buf1); 075 it.skip(); 076 it.skip(); 077 assertEquals(buf1.getLength(), buf1.getPosition(), "Position should be at end. Broken test."); 078 assertEquals(0, it.skip(), "Failed to skip null element with extended struct."); 079 assertEquals(0, it.skip(), "Failed to skip null element with extended struct."); 080 081 buf1.setPosition(0); 082 it = longer.iterator(buf1); 083 assertEquals(BigDecimal.ONE, it.next()); 084 assertEquals("foo", it.next()); 085 assertEquals(buf1.getLength(), buf1.getPosition(), "Position should be at end. Broken test."); 086 assertNull(it.next(), "Failed to skip null element with extended struct."); 087 assertNull(it.next(), "Failed to skip null element with extended struct."); 088 089 // test Struct 090 buf1.setPosition(0); 091 assertArrayEquals(val1, shorter.decode(buf1), "Simple struct decoding is broken."); 092 093 buf1.setPosition(0); 094 assertArrayEquals(Arrays.copyOf(val1, 4), longer.decode(buf1), 095 "Decoding short value with extended struct should append null elements."); 096 097 // test omission of trailing members 098 PositionedByteRange buf2 = new SimplePositionedMutableByteRange(7); 099 buf1.setPosition(0); 100 assertEquals(shortLen, longer.encode(buf2, val1), 101 "Encoding a short value with extended struct should have same result as using short struct."); 102 assertArrayEquals(buf1.getBytes(), buf2.getBytes(), 103 "Encoding a short value with extended struct should have same result as using short struct"); 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(buf1.getLength(), longer.encode(buf1, new Object[0]), 111 "Encoding null-truncated value wrote a surprising number of bytes."); 112 assertEquals(buf1.getLength(), longer.encode(buf1, val1), 113 "Encoding null-extended value wrote a surprising number of bytes."); 114 assertArrayEquals(buf1.getBytes(), buf2.getBytes(), "Encoded unexpected result."); 115 assertArrayEquals(val1, longer.decode(buf2), "Decoded unexpected result."); 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(buf1.getLength(), longer.encode(buf1, Arrays.copyOf(val2, 1)), 122 "Encoding null-truncated value wrote a surprising number of bytes."); 123 assertEquals(buf2.getLength(), longer.encode(buf2, val2), 124 "Encoding null-extended value wrote a surprising number of bytes."); 125 assertArrayEquals(buf1.getBytes(), buf2.getBytes(), "Encoded unexpected result."); 126 buf2.setPosition(0); 127 assertArrayEquals(val2, longer.decode(buf2), "Decoded unexpected result."); 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(buf1.getLength(), longer.encode(buf1, Arrays.copyOf(val3, 3)), 135 "Encoding null-truncated value wrote a surprising number of bytes."); 136 assertEquals(buf2.getLength(), longer.encode(buf2, val3), 137 "Encoding null-extended value wrote a surprising number of bytes."); 138 assertArrayEquals(buf1.getBytes(), buf2.getBytes(), "Encoded unexpected result."); 139 buf2.setPosition(0); 140 assertArrayEquals(val3, longer.decode(buf2), "Decoded unexpected result."); 141 } 142}