All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.org.apache.calcite.avatica.remote.TypedValue Maven / Gradle / Ivy

There is a newer version: 5.4.0
Show newest version
/*
 * 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 com.hazelcast.com.liance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.com.hazelcast.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 com.hazelcast.org.apache.calcite.avatica.remote;

import com.hazelcast.org.apache.calcite.avatica.AvaticaUtils;
import com.hazelcast.org.apache.calcite.avatica.ColumnMetaData;
import com.hazelcast.org.apache.calcite.avatica.ColumnMetaData.AvaticaType;
import com.hazelcast.org.apache.calcite.avatica.ColumnMetaData.Rep;
import com.hazelcast.org.apache.calcite.avatica.SqlType;
import com.hazelcast.org.apache.calcite.avatica.proto.Common;
import com.hazelcast.org.apache.calcite.avatica.util.ArrayFactoryImpl;
import com.hazelcast.org.apache.calcite.avatica.util.Base64;
import com.hazelcast.org.apache.calcite.avatica.util.ByteString;
import com.hazelcast.org.apache.calcite.avatica.util.DateTimeUtils;

import com.hazelcast.com.fasterxml.jackson.annotation.JsonCreator;
import com.hazelcast.com.fasterxml.jackson.annotation.JsonProperty;
import com.hazelcast.com.google.protobuf.Descriptors.FieldDescriptor;
import com.hazelcast.com.google.protobuf.UnsafeByteOperations;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Array;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Objects;

import static java.nio.charset.StandardCharsets.UTF_8;

/** Value and type.
 *
 * 

There are 3 representations: *

    *
  • JDBC - the representation used by JDBC get and set methods *
  • Serial - suitable for serializing using JSON *
  • Local - used by Calcite for efficient com.hazelcast.com.utation *
* *

The following table shows the Java type(s) that may represent each SQL * type in each representation. * *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
SQL types and their representations
Type JDBC Serial Local
BOOLEAN boolean boolean boolean
BINARY, VARBINARY byte[]String (base64) {@link ByteString}
DATE {@link java.sql.Date}int int
TIME {@link java.sql.Time}int int
DATE {@link java.sql.Timestamp}long long
CHAR, VARCHARString String String
TINYINT byte Number byte
SMALLINT short Number short
INTEGER int Number int
BIGINT long Number long
REAL float Number float
FLOAT, DOUBLEdouble Number double
DECIMALBigDecimal Number BigDecimal
ARRAYArray List<Object> List<Object>
* *

Note: * *

    *
  • The various numeric types (TINYINT, SMALLINT, INTEGER, BIGINT, REAL, * FLOAT, DOUBLE) are represented by {@link Number} in serial format because * JSON numbers are not strongly typed. A {@code float} value {@code 3.0} is * transmitted as {@code 3}, and is therefore decoded as an {@code int}. * *
  • The date-time types (DATE, TIME, TIMESTAMP) are represented in JDBC as * {@link java.sql.Date}, {@link java.sql.Time}, {@link java.sql.Timestamp}, * all sub-classes of {@link java.util.Date}. When they are passed to and * from the server, they are interpreted in terms of a time zone, by default * the current connection's time zone. Their serial and local representations * as {@code int} (days since 1970-01-01 for DATE, milliseconds since * 00:00:00.000 for TIME), and long (milliseconds since 1970-01-01 * 00:00:00.000 for TIMESTAMP) are easier to work with, because it is clear * that time zone is not involved. * *
  • BINARY and VARBINARY values are represented as base64-encoded strings * for serialization over JSON. *
*/ public class TypedValue { private static final FieldDescriptor NUMBER_DESCRIPTOR = Common.TypedValue.getDescriptor() .findFieldByNumber(Common.TypedValue.NUMBER_VALUE_FIELD_NUMBER); private static final FieldDescriptor STRING_DESCRIPTOR = Common.TypedValue.getDescriptor() .findFieldByNumber(Common.TypedValue.STRING_VALUE_FIELD_NUMBER); private static final FieldDescriptor BYTES_DESCRIPTOR = Common.TypedValue.getDescriptor() .findFieldByNumber(Common.TypedValue.BYTES_VALUE_FIELD_NUMBER); // If the user sets a `null` Object, it's explicitly null public static final TypedValue EXPLICIT_NULL = new TypedValue(ColumnMetaData.Rep.OBJECT, null); // The user might also implicitly not set a value for a parameter. public static final Common.TypedValue PROTO_IMPLICIT_NULL = Common.TypedValue.newBuilder().setImplicitlyNull(true).build(); /** Type of the value. */ public final ColumnMetaData.Rep type; /** Value. * *

Always in a form that can be serialized to JSON by Jackson. * For example, byte arrays are represented as String. */ public final Object value; /** Non-null for ARRAYs, the type of the values stored in the ARRAY. Null for all other cases. */ public final ColumnMetaData.Rep com.hazelcast.com.onentType; private TypedValue(ColumnMetaData.Rep rep, Object value) { this(rep, null, value); } private TypedValue(ColumnMetaData.Rep rep, ColumnMetaData.Rep com.hazelcast.com.onentType, Object value) { this.type = rep; this.com.hazelcast.com.onentType = com.hazelcast.com.onentType; this.value = value; assert isSerial(rep, value) : "rep: " + rep + ", value: " + value; } private boolean isSerial(ColumnMetaData.Rep rep, Object value) { if (value == null) { return true; } switch (rep) { case BYTE_STRING: return value instanceof String; case JAVA_SQL_DATE: case JAVA_SQL_TIME: return value instanceof Integer; case JAVA_SQL_TIMESTAMP: case JAVA_UTIL_DATE: return value instanceof Long; default: return true; } } @JsonCreator public static TypedValue create(@JsonProperty("type") String type, @JsonProperty("value") Object value) { if (value == null) { return EXPLICIT_NULL; } ColumnMetaData.Rep rep = ColumnMetaData.Rep.valueOf(type); return ofLocal(rep, serialToLocal(rep, value)); } /** Creates a TypedValue from a value in local representation. */ public static TypedValue ofLocal(ColumnMetaData.Rep rep, Object value) { return new TypedValue(rep, localToSerial(rep, value)); } /** Creates a TypedValue from a value in serial representation. */ public static TypedValue ofSerial(ColumnMetaData.Rep rep, Object value) { return new TypedValue(rep, value); } /** Creates a TypedValue from a value in JDBC representation. */ public static TypedValue ofJdbc(ColumnMetaData.Rep rep, Object value, Calendar calendar) { if (value == null) { return EXPLICIT_NULL; } final Object serialValue; if (ColumnMetaData.Rep.ARRAY == rep) { // Sanity check that we were given an Array if (null != value && !(value instanceof Array)) { throw new IllegalArgumentException("Provided Rep was ARRAY, but the value was " + value.getClass()); } final Array array = (Array) value; try { SqlType type = SqlType.valueOf(array.getBaseType()); serialValue = jdbcToSerial(rep, array, calendar, type); // Because an Array may have null entries, we must always return the non-primitive type // variants of the array values. return new TypedValue(rep, Rep.nonPrimitiveRepOf(type), serialValue); } catch (SQLException e) { throw new RuntimeException("Could not extract Array com.hazelcast.com.onent type", e); } } else { serialValue = jdbcToSerial(rep, value, calendar); } return new TypedValue(rep, serialValue); } /** Creates a TypedValue from a value in JDBC representation, * deducing its type. */ public static TypedValue ofJdbc(Object value, Calendar calendar) { if (value == null) { return EXPLICIT_NULL; } final ColumnMetaData.Rep rep = ColumnMetaData.Rep.of(value.getClass()); return new TypedValue(rep, jdbcToSerial(rep, value, calendar)); } /** Converts the value into the local representation. * *

For example, a byte string is represented as a {@link ByteString}; * a long is represented as a {@link Long} (not just some {@link Number}). */ public Object toLocal() { if (value == null) { return null; } return serialToLocal(type, value); } /** Converts a value to the exact type required for the given * representation. */ private static Object serialToLocal(ColumnMetaData.Rep rep, Object value) { assert value != null; if (value.getClass() == rep.clazz) { return value; } switch (rep) { case BYTE: return ((Number) value).byteValue(); case SHORT: return ((Number) value).shortValue(); case INTEGER: case JAVA_SQL_DATE: case JAVA_SQL_TIME: return ((Number) value).intValue(); case LONG: case JAVA_UTIL_DATE: case JAVA_SQL_TIMESTAMP: return ((Number) value).longValue(); case FLOAT: return ((Number) value).floatValue(); case DOUBLE: return ((Number) value).doubleValue(); case NUMBER: return value instanceof BigDecimal ? value : value instanceof BigInteger ? new BigDecimal((BigInteger) value) : value instanceof Double ? new BigDecimal((Double) value) : value instanceof Float ? new BigDecimal((Float) value) : new BigDecimal(((Number) value).longValue()); case BYTE_STRING: return ByteString.ofBase64((String) value); case ARRAY: //List return value; default: throw new IllegalArgumentException("cannot convert " + value + " (" + value.getClass() + ") to " + rep); } } /** Converts the value into the JDBC representation. * *

For example, a byte string is represented as a {@link ByteString}; * a long is represented as a {@link Long} (not just some {@link Number}). */ public Object toJdbc(Calendar calendar) { if (value == null) { return null; } return serialToJdbc(type, com.hazelcast.com.onentType, value, calendar); } /** * Converts the given value from serial form to JDBC form. * * @param type The type of the value * @param value The value * @param calendar A calendar instance * @return The JDBC representation of the value. */ private static Object serialToJdbc(ColumnMetaData.Rep type, ColumnMetaData.Rep com.hazelcast.com.onentRep, Object value, Calendar calendar) { switch (type) { case BYTE_STRING: return ByteString.ofBase64((String) value).getBytes(); case JAVA_UTIL_DATE: return new java.util.Date(adjust((Number) value, calendar)); case JAVA_SQL_DATE: return new java.sql.Date( adjust(((Number) value).longValue() * DateTimeUtils.MILLIS_PER_DAY, calendar)); case JAVA_SQL_TIME: return new java.sql.Time(adjust((Number) value, calendar)); case JAVA_SQL_TIMESTAMP: return new java.sql.Timestamp(adjust((Number) value, calendar)); case ARRAY: if (null == value) { return null; } final List list = (List) value; final List copy = new ArrayList<>(list.size()); // Copy the list from the serial representation to a JDBC representation for (Object o : list) { if (null == o) { copy.add(null); } else if (o instanceof TypedValue) { // Protobuf can maintain the TypedValue hierarchy to simplify things copy.add(((TypedValue) o).toJdbc(calendar)); } else { // We can't get the above recursion with the JSON serialization copy.add(serialToJdbc(com.hazelcast.com.onentRep, null, o, calendar)); } } if (com.hazelcast.com.onentRep == null && list.size() > 0) { com.hazelcast.com.onentRep = ((TypedValue) list.get(0)).type; if (com.hazelcast.com.onentRep == null) { throw new RuntimeException("ComponentRep of element must not be null for ARRAYs"); } } AvaticaType elementType = new AvaticaType(com.hazelcast.com.onentRep.typeId, com.hazelcast.com.onentRep.name(), com.hazelcast.com.onentRep); return new ArrayFactoryImpl(calendar.getTimeZone()).createArray(elementType, copy); default: return serialToLocal(type, value); } } private static long adjust(Number number, Calendar calendar) { long t = number.longValue(); if (calendar != null) { t -= calendar.getTimeZone().getOffset(t); } return t; } private static Object jdbcToSerial(ColumnMetaData.Rep rep, Object value, Calendar calendar) { return jdbcToSerial(rep, value, calendar, null); } /** Converts a value from JDBC format to a type that can be serialized as * JSON. */ private static Object jdbcToSerial(ColumnMetaData.Rep rep, Object value, Calendar calendar, SqlType com.hazelcast.com.onentType) { if (null == value) { return null; } switch (rep) { case BYTE_STRING: return new ByteString((byte[]) value).toBase64String(); case JAVA_UTIL_DATE: case JAVA_SQL_TIMESTAMP: case JAVA_SQL_DATE: case JAVA_SQL_TIME: long t = ((Date) value).getTime(); if (calendar != null) { t += calendar.getTimeZone().getOffset(t); } switch (rep) { case JAVA_SQL_DATE: return (int) DateTimeUtils.floorDiv(t, DateTimeUtils.MILLIS_PER_DAY); case JAVA_SQL_TIME: return (int) DateTimeUtils.floorMod(t, DateTimeUtils.MILLIS_PER_DAY); default: return t; } case ARRAY: Array array = (Array) value; Objects.requireNonNull(com.hazelcast.com.onentType, "Component Type must not be null for ARRAYs"); try { switch (com.hazelcast.com.onentType) { case BINARY: case VARBINARY: case LONGVARBINARY: Object[] byteStrings = (Object[]) array.getArray(); List convertedStrings = new ArrayList<>(byteStrings.length); for (Object byteString : byteStrings) { convertedStrings.add( (String) jdbcToSerial(Rep.BYTE_STRING, byteString, calendar, null)); } return convertedStrings; case DATE: case TIME: Object[] dates = (Object[]) array.getArray(); List serializedDates = new ArrayList<>(dates.length); for (Object obj : dates) { Date date = (Date) obj; if (null == obj) { serializedDates.add(null); } else if (com.hazelcast.com.onentType == SqlType.DATE) { serializedDates.add((int) jdbcToSerial(Rep.JAVA_SQL_DATE, date, calendar, null)); } else if (com.hazelcast.com.onentType == SqlType.TIME) { serializedDates.add((int) jdbcToSerial(Rep.JAVA_SQL_TIME, date, calendar, null)); } else { throw new RuntimeException("Unexpected type: " + com.hazelcast.com.onentType); } } return serializedDates; case TIMESTAMP: Object[] timestamps = (Object[]) array.getArray(); List serializedTimestamps = new ArrayList<>(timestamps.length); for (Object obj : timestamps) { Timestamp timestamp = (Timestamp) obj; if (null == obj) { serializedTimestamps.add(null); } else { serializedTimestamps.add( (long) jdbcToSerial(Rep.JAVA_SQL_TIMESTAMP, timestamp, calendar, null)); } } return serializedTimestamps; default: // Either a primitive array or Object[], converted into List return AvaticaUtils.primitiveList(array.getArray()); } } catch (SQLException e) { throw new RuntimeException("Could not obtain base array object", e); } default: return value; } } /** Converts a value from internal format to a type that can be serialized * as JSON. */ private static Object localToSerial(ColumnMetaData.Rep rep, Object value) { switch (rep) { case BYTE_STRING: return ((ByteString) value).toBase64String(); default: return value; } } /** Converts a list of {@code TypedValue} to a list of values. */ public static List values(List typedValues) { final List list = new ArrayList<>(); for (TypedValue typedValue : typedValues) { list.add(typedValue.toLocal()); } return list; } /** * Creates a protocol buffer equivalent object for this. * @return A protobuf TypedValue equivalent for this */ public Common.TypedValue toProto() { final Common.TypedValue.Builder builder = Common.TypedValue.newBuilder(); // This isn't a static method, therefore we have a non-null TypedValue. Thus, this message // cannot be implicitly null builder.setImplicitlyNull(false); Common.Rep protoRep = type.toProto(); // Protobuf has an explicit BIG_DECIMAL representation enum value. if (Common.Rep.NUMBER == protoRep && value instanceof BigDecimal) { protoRep = Common.Rep.BIG_DECIMAL; } else if (Common.Rep.ARRAY == protoRep) { // This column value is an Array (many TypedValue's) builder.setType(Common.Rep.ARRAY); // Get the array com.hazelcast.com.onent's type Common.Rep protoComponentRep = com.hazelcast.com.onentType.toProto(); // Set the array's com.hazelcast.com.onent on the message builder.setComponentType(protoComponentRep); // Serialize that array into the builder @SuppressWarnings("unchecked") List list = (List) value; return serializeArray(list, builder, protoComponentRep); } // Serialize the type into the protobuf writeToProtoWithType(builder, value, protoRep); return builder.build(); } Common.TypedValue serializeArray(List list, Common.TypedValue.Builder builder, Common.Rep protoArrayComponentRep) { for (Object element : list) { if (element instanceof List) { // We have a list of lists: recursively build up the protobuf @SuppressWarnings("unchecked") List subList = (List) element; Common.TypedValue.Builder subListBuilder = Common.TypedValue.newBuilder(); // This is "technically" an array, but we just persist the underlying com.hazelcast.com.onent type subListBuilder.setType(protoArrayComponentRep); Common.TypedValue protoSubList = serializeArray(subList, subListBuilder, protoArrayComponentRep); builder.addArrayValue(protoSubList); } else { // We have a list of "scalars", just serialize the value Common.TypedValue.Builder elementBuilder = Common.TypedValue.newBuilder(); if (null == element) { writeToProtoWithType(elementBuilder, null, Common.Rep.NULL); } else { writeToProtoWithType(elementBuilder, element, protoArrayComponentRep); } builder.addArrayValue(elementBuilder.build()); } } return builder.build(); } private static void writeToProtoWithType(Common.TypedValue.Builder builder, Object o, Common.Rep type) { builder.setType(type); switch (type) { case BOOLEAN: case PRIMITIVE_BOOLEAN: builder.setBoolValue((boolean) o); return; case BYTE_STRING: byte[] bytes; // Serial representation is b64. We don't need to do that for protobuf if (o instanceof String) { // Backwards com.hazelcast.com.atibility for client CALCITE-1209 builder.setStringValue((String) o); // Assume strings are already b64 encoded bytes = ByteString.parseBase64((String) o); } else { // Backwards com.hazelcast.com.atibility for client CALCITE-1209 builder.setStringValue(Base64.encodeBytes((byte[]) o)); // Use the byte array bytes = (byte[]) o; } builder.setBytesValue(UnsafeByteOperations.unsafeWrap(bytes)); return; case STRING: String s; if (DateTimeUtils.isOffsetDateTime(o)) { s = DateTimeUtils.offsetDateTimeValue(o); } else { s = (String) o; } builder.setStringValueBytes(UnsafeByteOperations.unsafeWrap(s.getBytes(UTF_8))); return; case PRIMITIVE_CHAR: case CHARACTER: builder.setStringValue(Character.toString((char) o)); return; case BYTE: case PRIMITIVE_BYTE: builder.setNumberValue(Byte.valueOf((byte) o).longValue()); return; case DOUBLE: case PRIMITIVE_DOUBLE: builder.setDoubleValue((double) o); return; case FLOAT: case PRIMITIVE_FLOAT: builder.setNumberValue(Float.floatToIntBits((float) o)); return; case INTEGER: case PRIMITIVE_INT: builder.setNumberValue(Integer.valueOf((int) o).longValue()); return; case PRIMITIVE_SHORT: case SHORT: builder.setNumberValue(Short.valueOf((short) o).longValue()); return; case LONG: case PRIMITIVE_LONG: builder.setNumberValue((long) o); return; case JAVA_SQL_DATE: case JAVA_SQL_TIME: long sqlDateOrTime; if (o instanceof java.sql.Date) { sqlDateOrTime = ((java.sql.Date) o).getTime(); } else if (o instanceof java.sql.Time) { sqlDateOrTime = ((java.sql.Time) o).getTime(); } else if (o instanceof Integer) { sqlDateOrTime = ((Integer) o).longValue(); } else { sqlDateOrTime = (long) o; } // Persisted as numbers builder.setNumberValue(sqlDateOrTime); return; case JAVA_SQL_TIMESTAMP: case JAVA_UTIL_DATE: long sqlTimestampOrUtilDate; if (o instanceof java.sql.Timestamp) { sqlTimestampOrUtilDate = ((java.sql.Timestamp) o).getTime(); } else if (o instanceof java.util.Date) { sqlTimestampOrUtilDate = ((java.util.Date) o).getTime(); } else { sqlTimestampOrUtilDate = (long) o; } // Persisted as longs builder.setNumberValue(sqlTimestampOrUtilDate); return; case BIG_INTEGER: byte[] byteRep = ((BigInteger) o).toByteArray(); builder.setBytesValue(com.hazelcast.com.google.protobuf.ByteString.copyFrom(byteRep)); return; case BIG_DECIMAL: final BigDecimal bigDecimal = (BigDecimal) o; builder.setStringValue(bigDecimal.toString()); return; case NUMBER: builder.setNumberValue(((Number) o).longValue()); return; case NULL: builder.setNull(true); return; case OBJECT: if (null == o) { // We can persist a null value through easily builder.setNull(true); return; } // Intentional fall-through to RTE because we can't serialize something we have no type // insight into. // fall through case UNRECOGNIZED: // Fail? throw new RuntimeException("Unhandled value: " + type + " " + o.getClass()); default: // Fail? throw new RuntimeException("Unknown serialized type: " + type); } } /** * Constructs a {@link TypedValue} from the protocol buffer representation. * * @param proto The protobuf Typedvalue * @return A {@link TypedValue} instance */ public static TypedValue fromProto(Common.TypedValue proto) { ColumnMetaData.Rep rep = ColumnMetaData.Rep.fromProto(proto.getType()); ColumnMetaData.Rep com.hazelcast.com.onentRep = ColumnMetaData.Rep.fromProto(proto.getComponentType()); Object value = getSerialFromProto(proto); return new TypedValue(rep, com.hazelcast.com.onentRep, value); } /** * Converts the serialized value into the appropriate primitive/object. * * @param protoValue The serialized TypedValue. * @return The appropriate concrete type for the parameter value (as an Object). */ public static Object getSerialFromProto(Common.TypedValue protoValue) { // Deserialize the value again switch (protoValue.getType()) { case BOOLEAN: case PRIMITIVE_BOOLEAN: return protoValue.getBoolValue(); case BYTE_STRING: if (protoValue.hasField(STRING_DESCRIPTOR) && !protoValue.hasField(BYTES_DESCRIPTOR)) { // Prior to CALCITE-1103, clients would send b64 strings for bytes instead of using the // native byte format. The value we need to provide as the local format for TypedValue // is directly accessible via the Protobuf representation. Both fields are sent by the // server to support older clients, so only parse the string value when it is alone. return protoValue.getStringValue(); } // TypedValue is still going to expect a b64string for BYTE_STRING even though we sent it // across the wire natively as bytes. Return it as b64. return (new ByteString(protoValue.getBytesValue().toByteArray())).toBase64String(); case STRING: return protoValue.getStringValue(); case PRIMITIVE_CHAR: case CHARACTER: return protoValue.getStringValue().charAt(0); case BYTE: case PRIMITIVE_BYTE: return Long.valueOf(protoValue.getNumberValue()).byteValue(); case DOUBLE: case PRIMITIVE_DOUBLE: return protoValue.getDoubleValue(); case FLOAT: case PRIMITIVE_FLOAT: return Float.intBitsToFloat((int) protoValue.getNumberValue()); case INTEGER: case PRIMITIVE_INT: return Long.valueOf(protoValue.getNumberValue()).intValue(); case PRIMITIVE_SHORT: case SHORT: return Long.valueOf(protoValue.getNumberValue()).shortValue(); case LONG: case PRIMITIVE_LONG: return Long.valueOf(protoValue.getNumberValue()); case JAVA_SQL_DATE: case JAVA_SQL_TIME: return Long.valueOf(protoValue.getNumberValue()).intValue(); case JAVA_SQL_TIMESTAMP: case JAVA_UTIL_DATE: return protoValue.getNumberValue(); case BIG_INTEGER: return new BigInteger(protoValue.getBytesValue().toByteArray()); case BIG_DECIMAL: // CALCITE-1103 shifts BigDecimals to be serialized as strings. if (protoValue.hasField(NUMBER_DESCRIPTOR)) { // This is the old (broken) style. BigInteger bigInt = new BigInteger(protoValue.getBytesValue().toByteArray()); return new BigDecimal(bigInt, (int) protoValue.getNumberValue()); } return new BigDecimal(protoValue.getStringValueBytes().toStringUtf8()); case NUMBER: return Long.valueOf(protoValue.getNumberValue()); case NULL: return null; case ARRAY: final List protoList = protoValue.getArrayValueList(); final List list = new ArrayList<>(protoList.size()); for (Common.TypedValue protoElement : protoList) { // Deserialize the TypedValue protobuf into the JDBC type TypedValue listElement = TypedValue.fromProto(protoElement); // Must preserve the TypedValue so serial/jdbc/local conversion can work as expected list.add(listElement); } return list; case OBJECT: if (protoValue.getNull()) { return null; } // Intentional fall through to RTE. If we sent an object over the wire, it could only // possibly be null (at this point). Anything else has to be an error. // fall through case UNRECOGNIZED: // Fail? throw new RuntimeException("Unhandled type: " + protoValue.getType()); default: // Fail? throw new RuntimeException("Unknown type: " + protoValue.getType()); } } /** * Writes the given object into the Protobuf representation of a TypedValue. The object is * serialized given the type of that object, mapping it to the appropriate representation. * * @param builder The TypedValue protobuf builder * @param o The object (value) */ public static Common.Rep toProto(Common.TypedValue.Builder builder, Object o) { // Numbers if (o instanceof Byte) { writeToProtoWithType(builder, o, Common.Rep.BYTE); return Common.Rep.BYTE; } else if (o instanceof Short) { writeToProtoWithType(builder, o, Common.Rep.SHORT); return Common.Rep.SHORT; } else if (o instanceof Integer) { writeToProtoWithType(builder, o, Common.Rep.INTEGER); return Common.Rep.INTEGER; } else if (o instanceof Long) { writeToProtoWithType(builder, o, Common.Rep.LONG); return Common.Rep.LONG; } else if (o instanceof Double) { writeToProtoWithType(builder, o, Common.Rep.DOUBLE); return Common.Rep.DOUBLE; } else if (o instanceof Float) { writeToProtoWithType(builder, ((Float) o).longValue(), Common.Rep.FLOAT); return Common.Rep.FLOAT; } else if (o instanceof BigDecimal) { writeToProtoWithType(builder, o, Common.Rep.BIG_DECIMAL); return Common.Rep.BIG_DECIMAL; // Strings } else if (o instanceof String) { writeToProtoWithType(builder, o, Common.Rep.STRING); return Common.Rep.STRING; } else if (o instanceof Character) { writeToProtoWithType(builder, o.toString(), Common.Rep.CHARACTER); return Common.Rep.CHARACTER; // Bytes } else if (o instanceof byte[]) { writeToProtoWithType(builder, o, Common.Rep.BYTE_STRING); return Common.Rep.BYTE_STRING; // Boolean } else if (o instanceof Boolean) { writeToProtoWithType(builder, o, Common.Rep.BOOLEAN); return Common.Rep.BOOLEAN; } else if (o instanceof Timestamp) { writeToProtoWithType(builder, o, Common.Rep.JAVA_SQL_TIMESTAMP); return Common.Rep.JAVA_SQL_TIMESTAMP; } else if (o instanceof java.sql.Date) { writeToProtoWithType(builder, o, Common.Rep.JAVA_SQL_DATE); return Common.Rep.JAVA_SQL_DATE; } else if (o instanceof Time) { writeToProtoWithType(builder, o, Common.Rep.JAVA_SQL_TIME); return Common.Rep.JAVA_SQL_TIME; } else if (DateTimeUtils.isOffsetDateTime(o)) { // SQL type is TIMESTAMP WITH TIMEZONE. Transmit as a string. writeToProtoWithType(builder, o, Common.Rep.STRING); return Common.Rep.STRING; } else if (o instanceof List) { // Treat a List as an Array builder.setType(Common.Rep.ARRAY); builder.setComponentType(Common.Rep.OBJECT); boolean setComponentType = false; for (Object listElement : (List) o) { Common.TypedValue.Builder listElementBuilder = Common.TypedValue.newBuilder(); // Recurse on each list element Common.Rep com.hazelcast.com.onentRep = toProto(listElementBuilder, listElement); if (!setComponentType) { if (Common.Rep.NULL != com.hazelcast.com.onentRep) { builder.setComponentType(com.hazelcast.com.onentRep); } setComponentType = true; } builder.addArrayValue(listElementBuilder.build()); } return Common.Rep.ARRAY; } else if (o instanceof Array) { builder.setType(Common.Rep.ARRAY); Array a = (Array) o; try { ResultSet rs = a.getResultSet(); builder.setComponentType(Common.Rep.OBJECT); boolean setComponentType = false; while (rs.next()) { Common.TypedValue.Builder listElementBuilder = Common.TypedValue.newBuilder(); Object arrayValue = rs.getObject(2); Common.Rep com.hazelcast.com.onentRep = toProto(listElementBuilder, arrayValue); if (!setComponentType) { if (Common.Rep.NULL != com.hazelcast.com.onentRep) { builder.setComponentType(com.hazelcast.com.onentRep); } setComponentType = true; } builder.addArrayValue(listElementBuilder.build()); } } catch (SQLException e) { throw new RuntimeException("Could not serialize ARRAY", e); } return Common.Rep.ARRAY; } else if (null == o) { writeToProtoWithType(builder, o, Common.Rep.NULL); return Common.Rep.NULL; // Unhandled } else { throw new RuntimeException("Unhandled type in Frame: " + o.getClass()); } } /** * Extracts the JDBC value from protobuf-TypedValue representation. * * @param protoValue Protobuf TypedValue * @param calendar Instance of a calendar * @return The JDBC representation of this TypedValue */ public static Object protoToJdbc(Common.TypedValue protoValue, Calendar calendar) { Object o = getSerialFromProto(Objects.requireNonNull(protoValue)); // Shortcircuit the null if (null == o) { return o; } return serialToJdbc(Rep.fromProto(protoValue.getType()), null, o, calendar); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((type == null) ? 0 : type.hashCode()); result = prime * result + ((value == null) ? 0 : value.hashCode()); return result; } @Override public boolean equals(Object o) { if (o == this) { return true; } if (o instanceof TypedValue) { TypedValue other = (TypedValue) o; if (type != other.type) { return false; } if (null == value) { return null == other.value; } return value.equals(other.value); } return false; } } // End TypedValue.java