| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to you under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.calcite.avatica.remote; |
| |
| import org.apache.calcite.avatica.ColumnMetaData; |
| import org.apache.calcite.avatica.ColumnMetaData.Rep; |
| import org.apache.calcite.avatica.ColumnMetaData.ScalarType; |
| import org.apache.calcite.avatica.proto.Common; |
| import org.apache.calcite.avatica.util.ArrayFactoryImpl; |
| import org.apache.calcite.avatica.util.ArrayImpl; |
| import org.apache.calcite.avatica.util.Base64; |
| import org.apache.calcite.avatica.util.ByteString; |
| import org.apache.calcite.avatica.util.DateTimeUtils; |
| import org.apache.calcite.avatica.util.Unsafe; |
| |
| import org.junit.Test; |
| |
| import java.math.BigDecimal; |
| import java.sql.Array; |
| import java.sql.Types; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.List; |
| |
| import static org.hamcrest.CoreMatchers.instanceOf; |
| import static org.hamcrest.CoreMatchers.is; |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertThat; |
| import static org.junit.Assert.assertTrue; |
| |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| |
| /** |
| * Test serialization of TypedValue. |
| */ |
| public class TypedValueTest { |
| |
| private void serializeAndEqualityCheck(TypedValue value) { |
| TypedValue copy = TypedValue.fromProto(value.toProto()); |
| |
| assertEquals(value.type, copy.type); |
| assertEquals(value.value, copy.value); |
| } |
| |
| @Test public void testBoolean() { |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.PRIMITIVE_BOOLEAN, true)); |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.BOOLEAN, Boolean.TRUE)); |
| } |
| |
| @Test public void testByte() { |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.PRIMITIVE_BYTE, (byte) 4)); |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.BYTE, Byte.valueOf((byte) 4))); |
| } |
| |
| @Test public void testShort() { |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.PRIMITIVE_SHORT, (short) 42)); |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.SHORT, Short.valueOf((short) 42))); |
| } |
| |
| @Test public void testInteger() { |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.PRIMITIVE_INT, (int) 42000)); |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.INTEGER, Integer.valueOf((int) 42000))); |
| } |
| |
| @Test public void testLong() { |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.PRIMITIVE_LONG, Long.MAX_VALUE)); |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.LONG, Long.valueOf(Long.MAX_VALUE))); |
| } |
| |
| @Test public void testFloat() { |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.PRIMITIVE_FLOAT, 3.14159f)); |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.FLOAT, Float.valueOf(3.14159f))); |
| } |
| |
| @Test public void testDouble() { |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.PRIMITIVE_DOUBLE, Double.MAX_VALUE)); |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.DOUBLE, Double.valueOf(Double.MAX_VALUE))); |
| } |
| |
| @Test public void testDecimal() { |
| final BigDecimal decimal = new BigDecimal("1.2345"); |
| final TypedValue decimalTypedValue = TypedValue.ofLocal(Rep.NUMBER, decimal); |
| serializeAndEqualityCheck(decimalTypedValue); |
| |
| final Common.TypedValue protoTypedValue = decimalTypedValue.toProto(); |
| assertEquals(Common.Rep.BIG_DECIMAL, protoTypedValue.getType()); |
| final String strValue = protoTypedValue.getStringValue(); |
| assertNotNull(strValue); |
| assertEquals(decimal.toPlainString(), strValue); |
| } |
| |
| @Test public void testChar() { |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.PRIMITIVE_CHAR, 'c')); |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.CHARACTER, Character.valueOf('c'))); |
| } |
| |
| @Test public void testString() { |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.STRING, "qwertyasdf")); |
| } |
| |
| @Test public void testByteString() { |
| serializeAndEqualityCheck( |
| TypedValue.ofLocal(Rep.BYTE_STRING, |
| new ByteString("qwertyasdf".getBytes(UTF_8)))); |
| } |
| |
| @Test public void testBase64() { |
| byte[] bytes = "qwertyasdf".getBytes(UTF_8); |
| // Plain bytes get put into protobuf for simplicitly |
| Common.TypedValue proto = Common.TypedValue.newBuilder().setBytesValue( |
| com.google.protobuf.ByteString.copyFrom(bytes)) |
| .setType(Common.Rep.BYTE_STRING).build(); |
| |
| // But we should get back a b64-string to make sure TypedValue doesn't get confused. |
| Object deserializedObj = TypedValue.getSerialFromProto(proto); |
| assertThat(deserializedObj, is(instanceOf(String.class))); |
| assertEquals(new ByteString(bytes).toBase64String(), (String) deserializedObj); |
| |
| // But we should get a non-b64 byte array as the JDBC representation |
| deserializedObj = |
| TypedValue.protoToJdbc(proto, DateTimeUtils.calendar()); |
| assertThat(deserializedObj, is(instanceOf(byte[].class))); |
| assertArrayEquals(bytes, (byte[]) deserializedObj); |
| } |
| |
| @Test public void testSqlDate() { |
| // days since epoch |
| serializeAndEqualityCheck(TypedValue.ofLocal(Rep.JAVA_SQL_DATE, 25)); |
| } |
| |
| @Test public void testUtilDate() { |
| serializeAndEqualityCheck( |
| TypedValue.ofLocal(Rep.JAVA_UTIL_DATE, System.currentTimeMillis())); |
| } |
| |
| @Test public void testSqlTime() { |
| // millis since epoch |
| serializeAndEqualityCheck( |
| TypedValue.ofLocal(Rep.JAVA_SQL_TIME, 42 * 1024 * 1024)); |
| } |
| |
| @Test public void testSqlTimestamp() { |
| serializeAndEqualityCheck( |
| TypedValue.ofLocal(Rep.JAVA_SQL_TIMESTAMP, 42L * 1024 * 1024 * 1024)); |
| } |
| |
| @Test public void testLegacyDecimalParsing() { |
| final BigDecimal decimal = new BigDecimal("123451234512345"); |
| final Calendar calendar = DateTimeUtils.calendar(); |
| |
| // CALCITE-1103 Decimals were (incorrectly) getting serialized as normal "numbers" which |
| // caused them to use the numberValue field. TypedValue should still be able to handle |
| // values like this (but large values will be truncated and return bad values). |
| Common.TypedValue oldProtoStyle = Common.TypedValue.newBuilder().setType(Common.Rep.NUMBER) |
| .setNumberValue(decimal.longValue()).build(); |
| |
| TypedValue fromProtoTv = TypedValue.fromProto(oldProtoStyle); |
| Object o = fromProtoTv.toJdbc(calendar); |
| assertEquals(decimal, o); |
| } |
| |
| @Test public void testProtobufBytesNotSentAsBase64() { |
| final byte[] bytes = "asdf".getBytes(UTF_8); |
| final byte[] b64Bytes = Base64.encodeBytes(bytes).getBytes(UTF_8); |
| TypedValue tv = TypedValue.ofLocal(Rep.BYTE_STRING, new ByteString(bytes)); |
| // JSON encodes it as base64 |
| assertEquals(new String(b64Bytes, UTF_8), tv.value); |
| |
| // Get the protobuf variant |
| Common.TypedValue protoTv = tv.toProto(); |
| Common.Rep protoRep = protoTv.getType(); |
| assertEquals(Common.Rep.BYTE_STRING, protoRep); |
| |
| // The pb variant should have the native bytes of the original value |
| com.google.protobuf.ByteString protoByteString = protoTv.getBytesValue(); |
| assertNotNull(protoByteString); |
| assertArrayEquals(bytes, protoByteString.toByteArray()); |
| |
| // We should have the b64 string as a backwards compatibility feature |
| assertEquals(new String(b64Bytes, UTF_8), |
| protoTv.getStringValue()); |
| } |
| |
| @Test public void testLegacyBase64StringEncodingForBytes() { |
| // CALCITE-1103 CALCITE-1209 We observed that binary data was being |
| // serialized as base-64 encoded strings instead of the native binary |
| // data type in protobufs. We need to still handle older clients sending |
| // data in this form. |
| final byte[] bytes = "asdf".getBytes(UTF_8); |
| final String base64Str = Base64.encodeBytes(bytes); |
| Common.TypedValue.Builder builder = Common.TypedValue.newBuilder(); |
| builder.setStringValue(base64Str); |
| builder.setType(Common.Rep.BYTE_STRING); |
| Common.TypedValue protoTv = builder.build(); |
| |
| TypedValue tv = TypedValue.fromProto(protoTv); |
| assertEquals(Rep.BYTE_STRING, tv.type); |
| assertEquals(base64Str, tv.value); |
| } |
| |
| @Test public void testArrays() { |
| List<Object> serialObj = Arrays.<Object>asList(1, 2, 3, 4); |
| ArrayImpl.Factory factory = new ArrayFactoryImpl(Unsafe.localCalendar().getTimeZone()); |
| ScalarType scalarType = ColumnMetaData.scalar(Types.INTEGER, "INTEGER", Rep.INTEGER); |
| Array a1 = factory.createArray(scalarType, serialObj); |
| TypedValue tv1 = TypedValue.ofJdbc(Rep.ARRAY, a1, Unsafe.localCalendar()); |
| Object jdbcObj = tv1.toJdbc(Unsafe.localCalendar()); |
| assertTrue("The JDBC object is an " + jdbcObj.getClass(), jdbcObj instanceof Array); |
| Object localObj = tv1.toLocal(); |
| assertTrue("The local object is an " + localObj.getClass(), localObj instanceof List); |
| Common.TypedValue protoTv1 = tv1.toProto(); |
| assertEquals(serialObj.size(), protoTv1.getArrayValueCount()); |
| TypedValue tv1Copy = TypedValue.fromProto(protoTv1); |
| Object jdbcObjCopy = tv1Copy.toJdbc(Unsafe.localCalendar()); |
| assertTrue("The JDBC object is an " + jdbcObjCopy.getClass(), jdbcObjCopy instanceof Array); |
| Object localObjCopy = tv1Copy.toLocal(); |
| assertTrue("The local object is an " + localObjCopy.getClass(), localObjCopy instanceof List); |
| } |
| } |
| |
| // End TypedValueTest.java |