org.h2.value.Transfer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of h2-mvstore Show documentation
Show all versions of h2-mvstore Show documentation
Fork of h2database to maintain Java 8 compatibility
The newest version!
/*
* Copyright 2004-2023 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (https://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.value;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Map.Entry;
import org.h2.api.ErrorCode;
import org.h2.api.IntervalQualifier;
import org.h2.engine.Constants;
import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.security.SHA256;
import org.h2.store.Data;
import org.h2.store.DataReader;
import org.h2.util.Bits;
import org.h2.util.DateTimeUtils;
import org.h2.util.IOUtils;
import org.h2.util.MathUtils;
import org.h2.util.NetUtils;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.h2.value.lob.LobData;
import org.h2.value.lob.LobDataDatabase;
import org.h2.value.lob.LobDataFetchOnDemand;
/**
* The transfer class is used to send and receive Value objects.
* It is used on both the client side, and on the server side.
*/
public final class Transfer {
private static final int BUFFER_SIZE = 64 * 1024;
private static final int LOB_MAGIC = 0x1234;
private static final int LOB_MAC_SALT_LENGTH = 16;
private static final int NULL = 0;
private static final int BOOLEAN = 1;
private static final int TINYINT = 2;
private static final int SMALLINT = 3;
private static final int INTEGER = 4;
private static final int BIGINT = 5;
private static final int NUMERIC = 6;
private static final int DOUBLE = 7;
private static final int REAL = 8;
private static final int TIME = 9;
private static final int DATE = 10;
private static final int TIMESTAMP = 11;
private static final int VARBINARY = 12;
private static final int VARCHAR = 13;
private static final int VARCHAR_IGNORECASE = 14;
private static final int BLOB = 15;
private static final int CLOB = 16;
private static final int ARRAY = 17;
private static final int JAVA_OBJECT = 19;
private static final int UUID = 20;
private static final int CHAR = 21;
private static final int GEOMETRY = 22;
// 1.4.192
private static final int TIMESTAMP_TZ = 24;
// 1.4.195
private static final int ENUM = 25;
// 1.4.198
private static final int INTERVAL = 26;
private static final int ROW = 27;
// 1.4.200
private static final int JSON = 28;
private static final int TIME_TZ = 29;
// 2.0.202
private static final int BINARY = 30;
private static final int DECFLOAT = 31;
private static final int[] VALUE_TO_TI = new int[Value.TYPE_COUNT + 1];
private static final int[] TI_TO_VALUE = new int[45];
static {
addType(-1, Value.UNKNOWN);
addType(NULL, Value.NULL);
addType(BOOLEAN, Value.BOOLEAN);
addType(TINYINT, Value.TINYINT);
addType(SMALLINT, Value.SMALLINT);
addType(INTEGER, Value.INTEGER);
addType(BIGINT, Value.BIGINT);
addType(NUMERIC, Value.NUMERIC);
addType(DOUBLE, Value.DOUBLE);
addType(REAL, Value.REAL);
addType(TIME, Value.TIME);
addType(DATE, Value.DATE);
addType(TIMESTAMP, Value.TIMESTAMP);
addType(VARBINARY, Value.VARBINARY);
addType(VARCHAR, Value.VARCHAR);
addType(VARCHAR_IGNORECASE, Value.VARCHAR_IGNORECASE);
addType(BLOB, Value.BLOB);
addType(CLOB, Value.CLOB);
addType(ARRAY, Value.ARRAY);
addType(JAVA_OBJECT, Value.JAVA_OBJECT);
addType(UUID, Value.UUID);
addType(CHAR, Value.CHAR);
addType(GEOMETRY, Value.GEOMETRY);
addType(TIMESTAMP_TZ, Value.TIMESTAMP_TZ);
addType(ENUM, Value.ENUM);
addType(26, Value.INTERVAL_YEAR);
addType(27, Value.INTERVAL_MONTH);
addType(28, Value.INTERVAL_DAY);
addType(29, Value.INTERVAL_HOUR);
addType(30, Value.INTERVAL_MINUTE);
addType(31, Value.INTERVAL_SECOND);
addType(32, Value.INTERVAL_YEAR_TO_MONTH);
addType(33, Value.INTERVAL_DAY_TO_HOUR);
addType(34, Value.INTERVAL_DAY_TO_MINUTE);
addType(35, Value.INTERVAL_DAY_TO_SECOND);
addType(36, Value.INTERVAL_HOUR_TO_MINUTE);
addType(37, Value.INTERVAL_HOUR_TO_SECOND);
addType(38, Value.INTERVAL_MINUTE_TO_SECOND);
addType(39, Value.ROW);
addType(40, Value.JSON);
addType(41, Value.TIME_TZ);
addType(42, Value.BINARY);
addType(43, Value.DECFLOAT);
}
private static void addType(int typeInformationType, int valueType) {
VALUE_TO_TI[valueType + 1] = typeInformationType;
TI_TO_VALUE[typeInformationType + 1] = valueType;
}
private final ReentrantLock lock = new ReentrantLock();
private Socket socket;
private DataInputStream in;
private DataOutputStream out;
private Session session;
private boolean ssl;
private int version;
private byte[] lobMacSalt;
/**
* Create a new transfer object for the specified session.
*
* @param session the session
* @param s the socket
*/
public Transfer(Session session, Socket s) {
this.session = session;
this.socket = s;
}
/**
* Locks this object with a reentrant lock.
*
*
* lock();
* try {
* ...
* } finally {
* unlock();
* }
*
*/
private void lock() {
lock.lock();
}
/**
* Unlocks this object.
*
* @see #lock()
*/
private void unlock() {
lock.unlock();
}
/**
* Initialize the transfer object. This method will try to open an input and
* output stream.
* @throws IOException on failure
*/
public void init() throws IOException {
lock();
try {
if (socket != null) {
in = new DataInputStream(
new BufferedInputStream(
socket.getInputStream(), Transfer.BUFFER_SIZE));
out = new DataOutputStream(
new BufferedOutputStream(
socket.getOutputStream(), Transfer.BUFFER_SIZE));
}
} finally {
unlock();
}
}
/**
* Write pending changes.
* @throws IOException on failure
*/
public void flush() throws IOException {
out.flush();
}
/**
* Write a boolean.
*
* @param x the value
* @return itself
* @throws IOException on failure
*/
public Transfer writeBoolean(boolean x) throws IOException {
out.writeByte((byte) (x ? 1 : 0));
return this;
}
/**
* Read a boolean.
*
* @return the value
* @throws IOException on failure
*/
public boolean readBoolean() throws IOException {
return in.readByte() != 0;
}
/**
* Write a byte.
*
* @param x the value
* @return itself
* @throws IOException on failure
*/
public Transfer writeByte(byte x) throws IOException {
out.writeByte(x);
return this;
}
/**
* Read a byte.
*
* @return the value
* @throws IOException on failure
*/
public byte readByte() throws IOException {
return in.readByte();
}
/**
* Write a short.
*
* @param x the value
* @return itself
* @throws IOException on failure
*/
private Transfer writeShort(short x) throws IOException {
out.writeShort(x);
return this;
}
/**
* Read a short.
*
* @return the value
* @throws IOException on failure
*/
private short readShort() throws IOException {
return in.readShort();
}
/**
* Write an int.
*
* @param x the value
* @return itself
* @throws IOException on failure
*/
public Transfer writeInt(int x) throws IOException {
out.writeInt(x);
return this;
}
/**
* Read an int.
*
* @return the value
* @throws IOException on failure
*/
public int readInt() throws IOException {
return in.readInt();
}
/**
* Write a long.
*
* @param x the value
* @return itself
* @throws IOException on failure
*/
public Transfer writeLong(long x) throws IOException {
out.writeLong(x);
return this;
}
/**
* Read a long.
*
* @return the value
* @throws IOException on failure
*/
public long readLong() throws IOException {
return in.readLong();
}
/**
* Write a double.
*
* @param i the value
* @return itself
* @throws IOException on failure
*/
private Transfer writeDouble(double i) throws IOException {
out.writeDouble(i);
return this;
}
/**
* Write a float.
*
* @param i the value
* @return itself
*/
private Transfer writeFloat(float i) throws IOException {
out.writeFloat(i);
return this;
}
/**
* Read a double.
*
* @return the value
* @throws IOException on failure
*/
private double readDouble() throws IOException {
return in.readDouble();
}
/**
* Read a float.
*
* @return the value
* @throws IOException on failure
*/
private float readFloat() throws IOException {
return in.readFloat();
}
/**
* Write a string. The maximum string length is Integer.MAX_VALUE.
*
* @param s the value
* @return itself
* @throws IOException on failure
*/
public Transfer writeString(String s) throws IOException {
if (s == null) {
out.writeInt(-1);
} else {
out.writeInt(s.length());
out.writeChars(s);
}
return this;
}
/**
* Read a string.
*
* @return the value
* @throws IOException on failure
*/
public String readString() throws IOException {
int len = in.readInt();
if (len == -1) {
return null;
}
StringBuilder buff = new StringBuilder(len);
for (int i = 0; i < len; i++) {
buff.append(in.readChar());
}
String s = buff.toString();
s = StringUtils.cache(s);
return s;
}
/**
* Write a byte array.
*
* @param data the value
* @return itself
* @throws IOException on failure
*/
public Transfer writeBytes(byte[] data) throws IOException {
if (data == null) {
writeInt(-1);
} else {
writeInt(data.length);
out.write(data);
}
return this;
}
/**
* Write a number of bytes.
*
* @param buff the value
* @param off the offset
* @param len the length
* @return itself
* @throws IOException on failure
*/
public Transfer writeBytes(byte[] buff, int off, int len) throws IOException {
out.write(buff, off, len);
return this;
}
/**
* Read a byte array.
*
* @return the value
* @throws IOException on failure
*/
public byte[] readBytes() throws IOException {
int len = readInt();
if (len == -1) {
return null;
}
byte[] b = Utils.newBytes(len);
in.readFully(b);
return b;
}
/**
* Read a number of bytes.
*
* @param buff the target buffer
* @param off the offset
* @param len the number of bytes to read
* @throws IOException on failure
*/
public void readBytes(byte[] buff, int off, int len) throws IOException {
in.readFully(buff, off, len);
}
/**
* Close the transfer object and the socket.
*/
public void close() {
lock();
try {
if (socket != null) {
try {
if (out != null) {
out.flush();
}
socket.close();
} catch (IOException e) {
DbException.traceThrowable(e);
} finally {
socket = null;
}
}
} finally {
unlock();
}
}
/**
* Write value type, precision, and scale.
*
* @param type data type information
* @return itself
* @throws IOException on failure
*/
public Transfer writeTypeInfo(TypeInfo type) throws IOException {
if (version >= Constants.TCP_PROTOCOL_VERSION_20) {
writeTypeInfo20(type);
} else {
writeTypeInfo19(type);
}
return this;
}
private void writeTypeInfo20(TypeInfo type) throws IOException {
int valueType = type.getValueType();
writeInt(VALUE_TO_TI[valueType + 1]);
switch (valueType) {
case Value.UNKNOWN:
case Value.NULL:
case Value.BOOLEAN:
case Value.TINYINT:
case Value.SMALLINT:
case Value.INTEGER:
case Value.BIGINT:
case Value.DATE:
case Value.UUID:
break;
case Value.CHAR:
case Value.VARCHAR:
case Value.VARCHAR_IGNORECASE:
case Value.BINARY:
case Value.VARBINARY:
case Value.DECFLOAT:
case Value.JAVA_OBJECT:
case Value.JSON:
writeInt((int) type.getDeclaredPrecision());
break;
case Value.CLOB:
case Value.BLOB:
writeLong(type.getDeclaredPrecision());
break;
case Value.NUMERIC:
writeInt((int) type.getDeclaredPrecision());
writeInt(type.getDeclaredScale());
writeBoolean(type.getExtTypeInfo() != null);
break;
case Value.REAL:
case Value.DOUBLE:
case Value.INTERVAL_YEAR:
case Value.INTERVAL_MONTH:
case Value.INTERVAL_DAY:
case Value.INTERVAL_HOUR:
case Value.INTERVAL_MINUTE:
case Value.INTERVAL_YEAR_TO_MONTH:
case Value.INTERVAL_DAY_TO_HOUR:
case Value.INTERVAL_DAY_TO_MINUTE:
case Value.INTERVAL_HOUR_TO_MINUTE:
writeBytePrecisionWithDefault(type.getDeclaredPrecision());
break;
case Value.TIME:
case Value.TIME_TZ:
case Value.TIMESTAMP:
case Value.TIMESTAMP_TZ:
writeByteScaleWithDefault(type.getDeclaredScale());
break;
case Value.INTERVAL_SECOND:
case Value.INTERVAL_DAY_TO_SECOND:
case Value.INTERVAL_HOUR_TO_SECOND:
case Value.INTERVAL_MINUTE_TO_SECOND:
writeBytePrecisionWithDefault(type.getDeclaredPrecision());
writeByteScaleWithDefault(type.getDeclaredScale());
break;
case Value.ENUM:
writeTypeInfoEnum(type);
break;
case Value.GEOMETRY:
writeTypeInfoGeometry(type);
break;
case Value.ARRAY:
writeInt((int) type.getDeclaredPrecision());
writeTypeInfo((TypeInfo) type.getExtTypeInfo());
break;
case Value.ROW:
writeTypeInfoRow(type);
break;
default:
throw DbException.getUnsupportedException("value type " + valueType);
}
}
private void writeBytePrecisionWithDefault(long precision) throws IOException {
writeByte(precision >= 0 ? (byte) precision : -1);
}
private void writeByteScaleWithDefault(int scale) throws IOException {
writeByte(scale >= 0 ? (byte) scale : -1);
}
private void writeTypeInfoEnum(TypeInfo type) throws IOException {
ExtTypeInfoEnum ext = (ExtTypeInfoEnum) type.getExtTypeInfo();
if (ext != null) {
int c = ext.getCount();
writeInt(c);
for (int i = 0; i < c; i++) {
writeString(ext.getEnumerator(i));
}
} else {
writeInt(0);
}
}
private void writeTypeInfoGeometry(TypeInfo type) throws IOException {
ExtTypeInfoGeometry ext = (ExtTypeInfoGeometry) type.getExtTypeInfo();
if (ext == null) {
writeByte((byte) 0);
} else {
int t = ext.getType();
Integer srid = ext.getSrid();
if (t == 0) {
if (srid == null) {
writeByte((byte) 0);
} else {
writeByte((byte) 2);
writeInt(srid);
}
} else {
if (srid == null) {
writeByte((byte) 1);
writeShort((short) t);
} else {
writeByte((byte) 3);
writeShort((short) t);
writeInt(srid);
}
}
}
}
private void writeTypeInfoRow(TypeInfo type) throws IOException {
Set> fields = ((ExtTypeInfoRow) type.getExtTypeInfo()).getFields();
writeInt(fields.size());
for (Map.Entry field : fields) {
writeString(field.getKey()).writeTypeInfo(field.getValue());
}
}
private void writeTypeInfo19(TypeInfo type) throws IOException {
int valueType = type.getValueType();
switch (valueType) {
case Value.BINARY:
valueType = Value.VARBINARY;
break;
case Value.DECFLOAT:
valueType = Value.NUMERIC;
break;
}
writeInt(VALUE_TO_TI[valueType + 1]).writeLong(type.getPrecision()).writeInt(type.getScale());
}
/**
* Read a type information.
*
* @return the type information
* @throws IOException on failure
*/
public TypeInfo readTypeInfo() throws IOException {
if (version >= Constants.TCP_PROTOCOL_VERSION_20) {
return readTypeInfo20();
} else {
return readTypeInfo19();
}
}
private TypeInfo readTypeInfo20() throws IOException {
int valueType = TI_TO_VALUE[readInt() + 1];
long precision = -1L;
int scale = -1;
ExtTypeInfo ext = null;
switch (valueType) {
case Value.UNKNOWN:
case Value.NULL:
case Value.BOOLEAN:
case Value.TINYINT:
case Value.SMALLINT:
case Value.INTEGER:
case Value.BIGINT:
case Value.DATE:
case Value.UUID:
break;
case Value.CHAR:
case Value.VARCHAR:
case Value.VARCHAR_IGNORECASE:
case Value.BINARY:
case Value.VARBINARY:
case Value.DECFLOAT:
case Value.JAVA_OBJECT:
case Value.JSON:
precision = readInt();
break;
case Value.CLOB:
case Value.BLOB:
precision = readLong();
break;
case Value.NUMERIC:
precision = readInt();
scale = readInt();
if (readBoolean()) {
ext = ExtTypeInfoNumeric.DECIMAL;
}
break;
case Value.REAL:
case Value.DOUBLE:
case Value.INTERVAL_YEAR:
case Value.INTERVAL_MONTH:
case Value.INTERVAL_DAY:
case Value.INTERVAL_HOUR:
case Value.INTERVAL_MINUTE:
case Value.INTERVAL_YEAR_TO_MONTH:
case Value.INTERVAL_DAY_TO_HOUR:
case Value.INTERVAL_DAY_TO_MINUTE:
case Value.INTERVAL_HOUR_TO_MINUTE:
precision = readByte();
break;
case Value.TIME:
case Value.TIME_TZ:
case Value.TIMESTAMP:
case Value.TIMESTAMP_TZ:
scale = readByte();
break;
case Value.INTERVAL_SECOND:
case Value.INTERVAL_DAY_TO_SECOND:
case Value.INTERVAL_HOUR_TO_SECOND:
case Value.INTERVAL_MINUTE_TO_SECOND:
precision = readByte();
scale = readByte();
break;
case Value.ENUM:
ext = readTypeInfoEnum();
break;
case Value.GEOMETRY:
ext = readTypeInfoGeometry();
break;
case Value.ARRAY:
precision = readInt();
ext = readTypeInfo();
break;
case Value.ROW:
ext = readTypeInfoRow();
break;
default:
throw DbException.getUnsupportedException("value type " + valueType);
}
return TypeInfo.getTypeInfo(valueType, precision, scale, ext);
}
private ExtTypeInfo readTypeInfoEnum() throws IOException {
ExtTypeInfo ext;
int c = readInt();
if (c > 0) {
String[] enumerators = new String[c];
for (int i = 0; i < c; i++) {
enumerators[i] = readString();
}
ext = new ExtTypeInfoEnum(enumerators);
} else {
ext = null;
}
return ext;
}
private ExtTypeInfo readTypeInfoGeometry() throws IOException {
ExtTypeInfo ext;
int e = readByte();
switch (e) {
case 0:
ext = null;
break;
case 1:
ext = new ExtTypeInfoGeometry(readShort(), null);
break;
case 2:
ext = new ExtTypeInfoGeometry(0, readInt());
break;
case 3:
ext = new ExtTypeInfoGeometry(readShort(), readInt());
break;
default:
throw DbException.getUnsupportedException("GEOMETRY type encoding " + e);
}
return ext;
}
private ExtTypeInfo readTypeInfoRow() throws IOException {
LinkedHashMap fields = new LinkedHashMap<>();
for (int i = 0, l = readInt(); i < l; i++) {
String name = readString();
if (fields.putIfAbsent(name, readTypeInfo()) != null) {
throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, name);
}
}
return new ExtTypeInfoRow(fields);
}
private TypeInfo readTypeInfo19() throws IOException {
return TypeInfo.getTypeInfo(TI_TO_VALUE[readInt() + 1], readLong(), readInt(), null);
}
/**
* Write a value.
*
* @param v the value
* @throws IOException on failure
*/
public void writeValue(Value v) throws IOException {
int type = v.getValueType();
switch (type) {
case Value.NULL:
writeInt(NULL);
break;
case Value.BINARY:
if (version >= Constants.TCP_PROTOCOL_VERSION_20) {
writeInt(BINARY);
writeBytes(v.getBytesNoCopy());
break;
}
//$FALL-THROUGH$
case Value.VARBINARY:
writeInt(VARBINARY);
writeBytes(v.getBytesNoCopy());
break;
case Value.JAVA_OBJECT:
writeInt(JAVA_OBJECT);
writeBytes(v.getBytesNoCopy());
break;
case Value.UUID: {
writeInt(UUID);
ValueUuid uuid = (ValueUuid) v;
writeLong(uuid.getHigh());
writeLong(uuid.getLow());
break;
}
case Value.BOOLEAN:
writeInt(BOOLEAN);
writeBoolean(v.getBoolean());
break;
case Value.TINYINT:
writeInt(TINYINT);
writeByte(v.getByte());
break;
case Value.TIME:
writeInt(TIME);
writeLong(((ValueTime) v).getNanos());
break;
case Value.TIME_TZ: {
ValueTimeTimeZone t = (ValueTimeTimeZone) v;
if (version >= Constants.TCP_PROTOCOL_VERSION_19) {
writeInt(TIME_TZ);
writeLong(t.getNanos());
writeInt(t.getTimeZoneOffsetSeconds());
} else {
writeInt(TIME);
/*
* Don't call SessionRemote.currentTimestamp(), it may require
* own remote call and old server will not return custom time
* zone anyway.
*/
ValueTimestampTimeZone current = session.isRemote()
? DateTimeUtils.currentTimestamp(DateTimeUtils.getTimeZone()) : session.currentTimestamp();
writeLong(DateTimeUtils.normalizeNanosOfDay(t.getNanos() +
(t.getTimeZoneOffsetSeconds() - current.getTimeZoneOffsetSeconds())
* DateTimeUtils.NANOS_PER_DAY));
}
break;
}
case Value.DATE:
writeInt(DATE);
writeLong(((ValueDate) v).getDateValue());
break;
case Value.TIMESTAMP: {
writeInt(TIMESTAMP);
ValueTimestamp ts = (ValueTimestamp) v;
writeLong(ts.getDateValue());
writeLong(ts.getTimeNanos());
break;
}
case Value.TIMESTAMP_TZ: {
writeInt(TIMESTAMP_TZ);
ValueTimestampTimeZone ts = (ValueTimestampTimeZone) v;
writeLong(ts.getDateValue());
writeLong(ts.getTimeNanos());
int timeZoneOffset = ts.getTimeZoneOffsetSeconds();
writeInt(version >= Constants.TCP_PROTOCOL_VERSION_19 //
? timeZoneOffset : timeZoneOffset / 60);
break;
}
case Value.DECFLOAT:
if (version >= Constants.TCP_PROTOCOL_VERSION_20) {
writeInt(DECFLOAT);
writeString(v.getString());
break;
}
//$FALL-THROUGH$
case Value.NUMERIC:
writeInt(NUMERIC);
writeString(v.getString());
break;
case Value.DOUBLE:
writeInt(DOUBLE);
writeDouble(v.getDouble());
break;
case Value.REAL:
writeInt(REAL);
writeFloat(v.getFloat());
break;
case Value.INTEGER:
writeInt(INTEGER);
writeInt(v.getInt());
break;
case Value.BIGINT:
writeInt(BIGINT);
writeLong(v.getLong());
break;
case Value.SMALLINT:
writeInt(SMALLINT);
if (version >= Constants.TCP_PROTOCOL_VERSION_20) {
writeShort(v.getShort());
} else {
writeInt(v.getShort());
}
break;
case Value.VARCHAR:
writeInt(VARCHAR);
writeString(v.getString());
break;
case Value.VARCHAR_IGNORECASE:
writeInt(VARCHAR_IGNORECASE);
writeString(v.getString());
break;
case Value.CHAR:
writeInt(CHAR);
writeString(v.getString());
break;
case Value.BLOB: {
writeInt(BLOB);
ValueBlob lob = (ValueBlob) v;
LobData lobData = lob.getLobData();
long length = lob.octetLength();
if (lobData instanceof LobDataDatabase) {
LobDataDatabase lobDataDatabase = (LobDataDatabase) lobData;
writeLong(-1);
writeInt(lobDataDatabase.getTableId());
writeLong(lobDataDatabase.getLobId());
writeBytes(calculateLobMac(lobDataDatabase.getLobId()));
writeLong(length);
break;
}
if (length < 0) {
throw DbException.get(
ErrorCode.CONNECTION_BROKEN_1, "length=" + length);
}
writeLong(length);
long written = IOUtils.copyAndCloseInput(lob.getInputStream(), out);
if (written != length) {
throw DbException.get(
ErrorCode.CONNECTION_BROKEN_1, "length:" + length + " written:" + written);
}
writeInt(LOB_MAGIC);
break;
}
case Value.CLOB: {
writeInt(CLOB);
ValueClob lob = (ValueClob) v;
LobData lobData = lob.getLobData();
long charLength = lob.charLength();
if (lobData instanceof LobDataDatabase) {
LobDataDatabase lobDataDatabase = (LobDataDatabase) lobData;
writeLong(-1);
writeInt(lobDataDatabase.getTableId());
writeLong(lobDataDatabase.getLobId());
writeBytes(calculateLobMac(lobDataDatabase.getLobId()));
if (version >= Constants.TCP_PROTOCOL_VERSION_20) {
writeLong(lob.octetLength());
}
writeLong(charLength);
break;
}
if (charLength < 0) {
throw DbException.get(
ErrorCode.CONNECTION_BROKEN_1, "length=" + charLength);
}
writeLong(charLength);
Reader reader = lob.getReader();
Data.copyString(reader, out);
writeInt(LOB_MAGIC);
break;
}
case Value.ARRAY: {
writeInt(ARRAY);
ValueArray va = (ValueArray) v;
Value[] list = va.getList();
int len = list.length;
writeInt(len);
for (Value value : list) {
writeValue(value);
}
break;
}
case Value.ROW: {
writeInt(version >= Constants.TCP_PROTOCOL_VERSION_18 ? ROW : ARRAY);
ValueRow va = (ValueRow) v;
Value[] list = va.getList();
int len = list.length;
writeInt(len);
for (Value value : list) {
writeValue(value);
}
break;
}
case Value.ENUM: {
writeInt(ENUM);
writeInt(v.getInt());
if (version < Constants.TCP_PROTOCOL_VERSION_20) {
writeString(v.getString());
}
break;
}
case Value.GEOMETRY:
writeInt(GEOMETRY);
writeBytes(v.getBytesNoCopy());
break;
case Value.INTERVAL_YEAR:
case Value.INTERVAL_MONTH:
case Value.INTERVAL_DAY:
case Value.INTERVAL_HOUR:
case Value.INTERVAL_MINUTE:
if (version >= Constants.TCP_PROTOCOL_VERSION_18) {
ValueInterval interval = (ValueInterval) v;
int ordinal = type - Value.INTERVAL_YEAR;
if (interval.isNegative()) {
ordinal = ~ordinal;
}
writeInt(INTERVAL);
writeByte((byte) ordinal);
writeLong(interval.getLeading());
} else {
writeInt(VARCHAR);
writeString(v.getString());
}
break;
case Value.INTERVAL_SECOND:
case Value.INTERVAL_YEAR_TO_MONTH:
case Value.INTERVAL_DAY_TO_HOUR:
case Value.INTERVAL_DAY_TO_MINUTE:
case Value.INTERVAL_DAY_TO_SECOND:
case Value.INTERVAL_HOUR_TO_MINUTE:
case Value.INTERVAL_HOUR_TO_SECOND:
case Value.INTERVAL_MINUTE_TO_SECOND:
if (version >= Constants.TCP_PROTOCOL_VERSION_18) {
ValueInterval interval = (ValueInterval) v;
int ordinal = type - Value.INTERVAL_YEAR;
if (interval.isNegative()) {
ordinal = ~ordinal;
}
writeInt(INTERVAL);
writeByte((byte) ordinal);
writeLong(interval.getLeading());
writeLong(interval.getRemaining());
} else {
writeInt(VARCHAR);
writeString(v.getString());
}
break;
case Value.JSON: {
writeInt(JSON);
writeBytes(v.getBytesNoCopy());
break;
}
default:
throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "type=" + type);
}
}
/**
* Read a value.
*
* @param columnType the data type of value, or {@code null}
* @return the value
* @throws IOException on failure
*/
public Value readValue(TypeInfo columnType) throws IOException {
int type = readInt();
switch (type) {
case NULL:
return ValueNull.INSTANCE;
case VARBINARY:
return ValueVarbinary.getNoCopy(readBytes());
case BINARY:
return ValueBinary.getNoCopy(readBytes());
case UUID:
return ValueUuid.get(readLong(), readLong());
case JAVA_OBJECT:
return ValueJavaObject.getNoCopy(readBytes());
case BOOLEAN:
return ValueBoolean.get(readBoolean());
case TINYINT:
return ValueTinyint.get(readByte());
case DATE:
return ValueDate.fromDateValue(readLong());
case TIME:
return ValueTime.fromNanos(readLong());
case TIME_TZ:
return ValueTimeTimeZone.fromNanos(readLong(), readInt());
case TIMESTAMP:
return ValueTimestamp.fromDateValueAndNanos(readLong(), readLong());
case TIMESTAMP_TZ: {
long dateValue = readLong(), timeNanos = readLong();
int timeZoneOffset = readInt();
return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos,
version >= Constants.TCP_PROTOCOL_VERSION_19 ? timeZoneOffset : timeZoneOffset * 60);
}
case NUMERIC:
return ValueNumeric.get(new BigDecimal(readString()));
case DOUBLE:
return ValueDouble.get(readDouble());
case REAL:
return ValueReal.get(readFloat());
case ENUM: {
int ordinal = readInt();
if (version >= Constants.TCP_PROTOCOL_VERSION_20) {
return ((ExtTypeInfoEnum) columnType.getExtTypeInfo()).getValue(ordinal, session);
}
return ValueEnumBase.get(readString(), ordinal);
}
case INTEGER:
return ValueInteger.get(readInt());
case BIGINT:
return ValueBigint.get(readLong());
case SMALLINT:
if (version >= Constants.TCP_PROTOCOL_VERSION_20) {
return ValueSmallint.get(readShort());
} else {
return ValueSmallint.get((short) readInt());
}
case VARCHAR:
return ValueVarchar.get(readString());
case VARCHAR_IGNORECASE:
return ValueVarcharIgnoreCase.get(readString());
case CHAR:
return ValueChar.get(readString());
case BLOB: {
long length = readLong();
if (length == -1) {
// fetch-on-demand LOB
int tableId = readInt();
long id = readLong();
byte[] hmac = readBytes();
long precision = readLong();
return new ValueBlob(new LobDataFetchOnDemand(session.getDataHandler(), tableId, id, hmac), precision);
}
Value v = session.getDataHandler().getLobStorage().createBlob(in, length);
int magic = readInt();
if (magic != LOB_MAGIC) {
throw DbException.get(
ErrorCode.CONNECTION_BROKEN_1, "magic=" + magic);
}
return v;
}
case CLOB: {
long charLength = readLong();
if (charLength == -1) {
// fetch-on-demand LOB
int tableId = readInt();
long id = readLong();
byte[] hmac = readBytes();
long octetLength = version >= Constants.TCP_PROTOCOL_VERSION_20 ? readLong() : -1L;
charLength = readLong();
return new ValueClob(new LobDataFetchOnDemand(session.getDataHandler(), tableId, id, hmac),
octetLength, charLength);
}
if (charLength < 0) {
throw DbException.get(
ErrorCode.CONNECTION_BROKEN_1, "length="+ charLength);
}
Value v = session.getDataHandler().getLobStorage().
createClob(new DataReader(in), charLength);
int magic = readInt();
if (magic != LOB_MAGIC) {
throw DbException.get(
ErrorCode.CONNECTION_BROKEN_1, "magic=" + magic);
}
return v;
}
case ARRAY: {
int len = readInt();
if (len < 0) {
// Unlikely, but possible with H2 1.4.200 and older versions
len = ~len;
readString();
}
if (columnType != null) {
TypeInfo elementType = (TypeInfo) columnType.getExtTypeInfo();
return ValueArray.get(elementType, readArrayElements(len, elementType), session);
}
return ValueArray.get(readArrayElements(len, null), session);
}
case ROW: {
int len = readInt();
Value[] list = new Value[len];
if (columnType != null) {
ExtTypeInfoRow extTypeInfoRow = (ExtTypeInfoRow) columnType.getExtTypeInfo();
Iterator> fields = extTypeInfoRow.getFields().iterator();
for (int i = 0; i < len; i++) {
list[i] = readValue(fields.next().getValue());
}
return ValueRow.get(columnType, list);
}
for (int i = 0; i < len; i++) {
list[i] = readValue(null);
}
return ValueRow.get(list);
}
case GEOMETRY:
return ValueGeometry.get(readBytes());
case INTERVAL: {
int ordinal = readByte();
boolean negative = ordinal < 0;
if (negative) {
ordinal = ~ordinal;
}
return ValueInterval.from(IntervalQualifier.valueOf(ordinal), negative, readLong(),
ordinal < 5 ? 0 : readLong());
}
case JSON:
// Do not trust the value
return ValueJson.fromJson(readBytes());
case DECFLOAT: {
String s = readString();
switch (s) {
case "-Infinity":
return ValueDecfloat.NEGATIVE_INFINITY;
case "Infinity":
return ValueDecfloat.POSITIVE_INFINITY;
case "NaN":
return ValueDecfloat.NAN;
default:
return ValueDecfloat.get(new BigDecimal(s));
}
}
default:
throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "type=" + type);
}
}
private Value[] readArrayElements(int len, TypeInfo elementType) throws IOException {
Value[] list = new Value[len];
for (int i = 0; i < len; i++) {
list[i] = readValue(elementType);
}
return list;
}
/**
* Read a row count.
*
* @return the row count
* @throws IOException on failure
*/
public long readRowCount() throws IOException {
return version >= Constants.TCP_PROTOCOL_VERSION_20 ? readLong() : readInt();
}
/**
* Write a row count.
*
* @param rowCount the row count
* @return itself
* @throws IOException on failure
*/
public Transfer writeRowCount(long rowCount) throws IOException {
return version >= Constants.TCP_PROTOCOL_VERSION_20 ? writeLong(rowCount)
: writeInt(rowCount < Integer.MAX_VALUE ? (int) rowCount : Integer.MAX_VALUE);
}
/**
* Get the socket.
*
* @return the socket
*/
public Socket getSocket() {
return socket;
}
/**
* Set the session.
*
* @param session the session
*/
public void setSession(Session session) {
this.session = session;
}
/**
* Enable or disable SSL.
*
* @param ssl the new value
*/
public void setSSL(boolean ssl) {
this.ssl = ssl;
}
/**
* Open a new connection to the same address and port as this one.
*
* @return the new transfer object
* @throws IOException on failure
*/
public Transfer openNewConnection() throws IOException {
InetAddress address = socket.getInetAddress();
int port = socket.getPort();
Socket s2 = NetUtils.createSocket(address, port, ssl);
Transfer trans = new Transfer(null, s2);
trans.setSSL(ssl);
return trans;
}
public void setVersion(int version) {
this.version = version;
}
public int getVersion() {
return version;
}
public boolean isClosed() {
lock();
try {
return socket == null || socket.isClosed();
} finally {
unlock();
}
}
/**
* Verify the HMAC.
*
* @param hmac the message authentication code
* @param lobId the lobId
* @throws DbException if the HMAC does not match
*/
public void verifyLobMac(byte[] hmac, long lobId) {
byte[] result = calculateLobMac(lobId);
if (!Utils.compareSecure(hmac, result)) {
throw DbException.get(ErrorCode.CONNECTION_BROKEN_1,
"Invalid lob hmac; possibly the connection was re-opened internally");
}
}
private byte[] calculateLobMac(long lobId) {
if (lobMacSalt == null) {
lobMacSalt = MathUtils.secureRandomBytes(LOB_MAC_SALT_LENGTH);
}
byte[] data = new byte[8];
Bits.writeLong(data, 0, lobId);
return SHA256.getHashWithSalt(data, lobMacSalt);
}
}