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

org.h2.store.Data Maven / Gradle / Ivy

There is a newer version: 1.0.0-beta2
Show newest version
/*
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (https://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 *
 * The variable size number format code is a port from SQLite,
 * but stored in reverse order (least significant bits in the first byte).
 */
package org.h2.store;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;

import org.h2.api.ErrorCode;
import org.h2.api.IntervalQualifier;
import org.h2.engine.Constants;
import org.h2.message.DbException;
import org.h2.result.ResultInterface;
import org.h2.result.SimpleResult;
import org.h2.util.Bits;
import org.h2.util.DateTimeUtils;
import org.h2.util.JdbcUtils;
import org.h2.util.MathUtils;
import org.h2.util.Utils;
import org.h2.value.TypeInfo;
import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueBoolean;
import org.h2.value.ValueByte;
import org.h2.value.ValueBytes;
import org.h2.value.ValueCollectionBase;
import org.h2.value.ValueDate;
import org.h2.value.ValueDecimal;
import org.h2.value.ValueDouble;
import org.h2.value.ValueFloat;
import org.h2.value.ValueGeometry;
import org.h2.value.ValueInt;
import org.h2.value.ValueInterval;
import org.h2.value.ValueJavaObject;
import org.h2.value.ValueJson;
import org.h2.value.ValueLob;
import org.h2.value.ValueLobDb;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
import org.h2.value.ValueResultSet;
import org.h2.value.ValueRow;
import org.h2.value.ValueShort;
import org.h2.value.ValueString;
import org.h2.value.ValueStringFixed;
import org.h2.value.ValueStringIgnoreCase;
import org.h2.value.ValueTime;
import org.h2.value.ValueTimeTimeZone;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampTimeZone;
import org.h2.value.ValueUuid;

/**
 * This class represents a byte buffer that contains persistent data of a page.
 *
 * @author Thomas Mueller
 * @author Noel Grandin
 * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
 */
public class Data {

    /**
     * The length of an integer value.
     */
    public static final int LENGTH_INT = 4;

    /**
     * The length of a long value.
     */
    public static final int LENGTH_LONG = 8;

    private static final byte NULL = 0;
    private static final byte BYTE = 2;
    private static final byte SHORT = 3;
    private static final byte INT = 4;
    private static final byte LONG = 5;
    private static final byte DECIMAL = 6;
    private static final byte DOUBLE = 7;
    private static final byte FLOAT = 8;
    private static final byte TIME = 9;
    private static final byte DATE = 10;
    private static final byte TIMESTAMP = 11;
    private static final byte BYTES = 12;
    private static final byte STRING = 13;
    private static final byte STRING_IGNORECASE = 14;
    private static final byte BLOB = 15;
    private static final byte CLOB = 16;
    private static final byte ARRAY = 17;
    private static final byte RESULT_SET = 18;
    private static final byte JAVA_OBJECT = 19;
    private static final byte UUID = 20;
    private static final byte STRING_FIXED = 21;
    private static final byte GEOMETRY = 22;
    private static final byte TIMESTAMP_TZ = 24;
    private static final byte ENUM = 25;
    private static final byte INTERVAL = 26;
    private static final byte ROW = 27;
    private static final byte INT_0_15 = 32;
    private static final byte LONG_0_7 = 48;
    private static final byte DECIMAL_0_1 = 56;
    private static final byte DECIMAL_SMALL_0 = 58;
    private static final byte DECIMAL_SMALL = 59;
    private static final byte DOUBLE_0_1 = 60;
    private static final byte FLOAT_0_1 = 62;
    private static final byte BOOLEAN_FALSE = 64;
    private static final byte BOOLEAN_TRUE = 65;
    private static final byte INT_NEG = 66;
    private static final byte LONG_NEG = 67;
    private static final byte STRING_0_31 = 68;
    private static final int BYTES_0_31 = 100;
    private static final int LOCAL_TIME = 132;
    private static final int LOCAL_DATE = 133;
    private static final int LOCAL_TIMESTAMP = 134;
    private static final int CUSTOM_DATA_TYPE = 135;
    private static final int JSON = 136;
    private static final int TIMESTAMP_TZ_2 = 137;
    private static final int TIME_TZ = 138;

    private static final long MILLIS_PER_MINUTE = 1000 * 60;

    /**
     * Raw offset doesn't change during DST transitions, but changes during
     * other transitions that some time zones have. H2 1.4.193 and later
     * versions use zone offset that is valid for startup time for performance
     * reasons. Datetime storage code of PageStore has issues with all time zone
     * transitions, so this buggy logic is preserved as is too.
     */
    private static int zoneOffsetMillis = new GregorianCalendar().get(Calendar.ZONE_OFFSET);

    /**
     * The data itself.
     */
    private byte[] data;

    /**
     * The current write or read position.
     */
    private int pos;

    /**
     * The data handler responsible for lob objects.
     */
    private final DataHandler handler;

    private final boolean storeLocalTime;

    private Data(DataHandler handler, byte[] data, boolean storeLocalTime) {
        this.handler = handler;
        this.data = data;
        this.storeLocalTime = storeLocalTime;
    }

    /**
     * Update an integer at the given position.
     * The current position is not change.
     *
     * @param pos the position
     * @param x the value
     */
    public void setInt(int pos, int x) {
        Bits.writeInt(data, pos, x);
    }

    /**
     * Write an integer at the current position.
     * The current position is incremented.
     *
     * @param x the value
     */
    public void writeInt(int x) {
        Bits.writeInt(data, pos, x);
        pos += 4;
    }

    /**
     * Read an integer at the current position.
     * The current position is incremented.
     *
     * @return the value
     */
    public int readInt() {
        int x = Bits.readInt(data, pos);
        pos += 4;
        return x;
    }

    /**
     * Get the length of a String. This includes the bytes required to encode
     * the length.
     *
     * @param s the string
     * @return the number of bytes required
     */
    public static int getStringLen(String s) {
        int len = s.length();
        return getStringWithoutLengthLen(s, len) + getVarIntLen(len);
    }

    /**
     * Calculate the length of String, excluding the bytes required to encode
     * the length.
     * 

* For performance reasons the internal representation of a String is * similar to UTF-8, but not exactly UTF-8. * * @param s the string * @param len the length of the string * @return the number of bytes required */ private static int getStringWithoutLengthLen(String s, int len) { int plus = 0; for (int i = 0; i < len; i++) { char c = s.charAt(i); if (c >= 0x800) { plus += 2; } else if (c >= 0x80) { plus++; } } return len + plus; } /** * Read a String value. * The current position is incremented. * * @return the value */ public String readString() { int len = readVarInt(); return readString(len); } /** * Read a String from the byte array. *

* For performance reasons the internal representation of a String is * similar to UTF-8, but not exactly UTF-8. * * @param len the length of the resulting string * @return the String */ private String readString(int len) { byte[] buff = data; int p = pos; char[] chars = new char[len]; for (int i = 0; i < len; i++) { int x = buff[p++] & 0xff; if (x < 0x80) { chars[i] = (char) x; } else if (x >= 0xe0) { chars[i] = (char) (((x & 0xf) << 12) + ((buff[p++] & 0x3f) << 6) + (buff[p++] & 0x3f)); } else { chars[i] = (char) (((x & 0x1f) << 6) + (buff[p++] & 0x3f)); } } pos = p; return new String(chars); } /** * Write a String. * The current position is incremented. * * @param s the value */ public void writeString(String s) { int len = s.length(); writeVarInt(len); writeStringWithoutLength(s, len); } /** * Write a String. *

* For performance reasons the internal representation of a String is * similar to UTF-8, but not exactly UTF-8. * * @param s the string * @param len the number of characters to write */ private void writeStringWithoutLength(String s, int len) { int p = pos; byte[] buff = data; for (int i = 0; i < len; i++) { int c = s.charAt(i); if (c < 0x80) { buff[p++] = (byte) c; } else if (c >= 0x800) { buff[p++] = (byte) (0xe0 | (c >> 12)); buff[p++] = (byte) (((c >> 6) & 0x3f)); buff[p++] = (byte) (c & 0x3f); } else { buff[p++] = (byte) (0xc0 | (c >> 6)); buff[p++] = (byte) (c & 0x3f); } } pos = p; } private void writeStringWithoutLength(char[] chars, int len) { int p = pos; byte[] buff = data; for (int i = 0; i < len; i++) { int c = chars[i]; if (c < 0x80) { buff[p++] = (byte) c; } else if (c >= 0x800) { buff[p++] = (byte) (0xe0 | (c >> 12)); buff[p++] = (byte) (((c >> 6) & 0x3f)); buff[p++] = (byte) (c & 0x3f); } else { buff[p++] = (byte) (0xc0 | (c >> 6)); buff[p++] = (byte) (c & 0x3f); } } pos = p; } /** * Create a new buffer for the given handler. The * handler will decide what type of buffer is created. * * @param handler the data handler * @param capacity the initial capacity of the buffer * @param storeLocalTime * store DATE, TIME, and TIMESTAMP values with local time storage * format * @return the buffer */ public static Data create(DataHandler handler, int capacity, boolean storeLocalTime) { return new Data(handler, new byte[capacity], storeLocalTime); } /** * Create a new buffer using the given data for the given handler. The * handler will decide what type of buffer is created. * * @param handler the data handler * @param buff the data * @param storeLocalTime * store DATE, TIME, and TIMESTAMP values with local time storage * format * @return the buffer */ public static Data create(DataHandler handler, byte[] buff, boolean storeLocalTime) { return new Data(handler, buff, storeLocalTime); } /** * Get the current write position of this buffer, which is the current * length. * * @return the length */ public int length() { return pos; } /** * Get the byte array used for this page. * * @return the byte array */ public byte[] getBytes() { return data; } /** * Set the position to 0. */ public void reset() { pos = 0; } /** * Append a number of bytes to this buffer. * * @param buff the data * @param off the offset in the data * @param len the length in bytes */ public void write(byte[] buff, int off, int len) { System.arraycopy(buff, off, data, pos, len); pos += len; } /** * Copy a number of bytes to the given buffer from the current position. The * current position is incremented accordingly. * * @param buff the output buffer * @param off the offset in the output buffer * @param len the number of bytes to copy */ public void read(byte[] buff, int off, int len) { System.arraycopy(data, pos, buff, off, len); pos += len; } /** * Append one single byte. * * @param x the value */ public void writeByte(byte x) { data[pos++] = x; } /** * Read one single byte. * * @return the value */ public byte readByte() { return data[pos++]; } /** * Read a long value. This method reads two int values and combines them. * * @return the long value */ public long readLong() { long x = Bits.readLong(data, pos); pos += 8; return x; } /** * Append a long value. This method writes two int values. * * @param x the value */ public void writeLong(long x) { Bits.writeLong(data, pos, x); pos += 8; } /** * Append a value. * * @param v the value */ public void writeValue(Value v) { int start = pos; if (v == ValueNull.INSTANCE) { data[pos++] = NULL; return; } int type = v.getValueType(); switch (type) { case Value.BOOLEAN: writeByte(v.getBoolean() ? BOOLEAN_TRUE : BOOLEAN_FALSE); break; case Value.BYTE: writeByte(BYTE); writeByte(v.getByte()); break; case Value.SHORT: writeByte(SHORT); writeShortInt(v.getShort()); break; case Value.ENUM: case Value.INT: { int x = v.getInt(); if (x < 0) { writeByte(INT_NEG); writeVarInt(-x); } else if (x < 16) { writeByte((byte) (INT_0_15 + x)); } else { writeByte(type == Value.INT ? INT : ENUM); writeVarInt(x); } break; } case Value.LONG: { long x = v.getLong(); if (x < 0) { writeByte(LONG_NEG); writeVarLong(-x); } else if (x < 8) { writeByte((byte) (LONG_0_7 + x)); } else { writeByte(LONG); writeVarLong(x); } break; } case Value.DECIMAL: { BigDecimal x = v.getBigDecimal(); if (BigDecimal.ZERO.equals(x)) { writeByte(DECIMAL_0_1); } else if (BigDecimal.ONE.equals(x)) { writeByte((byte) (DECIMAL_0_1 + 1)); } else { int scale = x.scale(); BigInteger b = x.unscaledValue(); int bits = b.bitLength(); if (bits <= 63) { if (scale == 0) { writeByte(DECIMAL_SMALL_0); writeVarLong(b.longValue()); } else { writeByte(DECIMAL_SMALL); writeVarInt(scale); writeVarLong(b.longValue()); } } else { writeByte(DECIMAL); writeVarInt(scale); byte[] bytes = b.toByteArray(); writeVarInt(bytes.length); write(bytes, 0, bytes.length); } } break; } case Value.TIME: if (storeLocalTime) { writeByte((byte) LOCAL_TIME); ValueTime t = (ValueTime) v; long nanos = t.getNanos(); long millis = nanos / 1_000_000; nanos -= millis * 1_000_000; writeVarLong(millis); writeVarInt((int) nanos); } else { writeByte(TIME); writeVarLong(v.getTime(null).getTime() + zoneOffsetMillis); } break; case Value.TIME_TZ: { writeByte((byte) TIME_TZ); ValueTimeTimeZone ts = (ValueTimeTimeZone) v; long nanosOfDay = ts.getNanos(); writeVarInt((int) (nanosOfDay / DateTimeUtils.NANOS_PER_SECOND)); writeVarInt((int) (nanosOfDay % DateTimeUtils.NANOS_PER_SECOND)); writeTimeZone(ts.getTimeZoneOffsetSeconds()); break; } case Value.DATE: { if (storeLocalTime) { writeByte((byte) LOCAL_DATE); long x = ((ValueDate) v).getDateValue(); writeVarLong(x); } else { writeByte(DATE); long x = v.getDate(null).getTime() + zoneOffsetMillis; writeVarLong(x / MILLIS_PER_MINUTE); } break; } case Value.TIMESTAMP: { if (storeLocalTime) { writeByte((byte) LOCAL_TIMESTAMP); ValueTimestamp ts = (ValueTimestamp) v; long dateValue = ts.getDateValue(); writeVarLong(dateValue); long nanos = ts.getTimeNanos(); long millis = nanos / 1_000_000; nanos -= millis * 1_000_000; writeVarLong(millis); writeVarInt((int) nanos); } else { Timestamp ts = v.getTimestamp(null); writeByte(TIMESTAMP); writeVarLong(ts.getTime() + zoneOffsetMillis); writeVarInt(ts.getNanos() % 1_000_000); } break; } case Value.TIMESTAMP_TZ: { ValueTimestampTimeZone ts = (ValueTimestampTimeZone) v; int timeZoneOffset = ts.getTimeZoneOffsetSeconds(); if (timeZoneOffset % 60 == 0) { writeByte(TIMESTAMP_TZ); writeVarLong(ts.getDateValue()); writeVarLong(ts.getTimeNanos()); writeVarInt(timeZoneOffset / 60); } else { writeByte((byte) TIMESTAMP_TZ_2); writeVarLong(ts.getDateValue()); writeVarLong(ts.getTimeNanos()); writeTimeZone(timeZoneOffset); } break; } case Value.GEOMETRY: // fall though case Value.JAVA_OBJECT: { writeByte(type == Value.GEOMETRY ? GEOMETRY : JAVA_OBJECT); byte[] b = v.getBytesNoCopy(); int len = b.length; writeVarInt(len); write(b, 0, len); break; } case Value.BYTES: { byte[] b = v.getBytesNoCopy(); int len = b.length; if (len < 32) { writeByte((byte) (BYTES_0_31 + len)); write(b, 0, len); } else { writeByte(BYTES); writeVarInt(len); write(b, 0, len); } break; } case Value.UUID: { writeByte(UUID); ValueUuid uuid = (ValueUuid) v; writeLong(uuid.getHigh()); writeLong(uuid.getLow()); break; } case Value.STRING: { String s = v.getString(); int len = s.length(); if (len < 32) { writeByte((byte) (STRING_0_31 + len)); writeStringWithoutLength(s, len); } else { writeByte(STRING); writeString(s); } break; } case Value.STRING_IGNORECASE: writeByte(STRING_IGNORECASE); writeString(v.getString()); break; case Value.STRING_FIXED: writeByte(STRING_FIXED); writeString(v.getString()); break; case Value.DOUBLE: { double x = v.getDouble(); if (x == 1.0d) { writeByte((byte) (DOUBLE_0_1 + 1)); } else { long d = Double.doubleToLongBits(x); if (d == ValueDouble.ZERO_BITS) { writeByte(DOUBLE_0_1); } else { writeByte(DOUBLE); writeVarLong(Long.reverse(d)); } } break; } case Value.FLOAT: { float x = v.getFloat(); if (x == 1.0f) { writeByte((byte) (FLOAT_0_1 + 1)); } else { int f = Float.floatToIntBits(x); if (f == ValueFloat.ZERO_BITS) { writeByte(FLOAT_0_1); } else { writeByte(FLOAT); writeVarInt(Integer.reverse(f)); } } break; } case Value.BLOB: case Value.CLOB: { writeByte(type == Value.BLOB ? BLOB : CLOB); if (v instanceof ValueLob) { ValueLob lob = (ValueLob) v; byte[] small = lob.getSmall(); if (small == null) { int t = -1; if (!lob.isLinkedToTable()) { t = -2; } writeVarInt(t); writeVarInt(lob.getTableId()); writeVarInt(lob.getObjectId()); writeVarLong(lob.getType().getPrecision()); writeByte((byte) (lob.isCompressed() ? 1 : 0)); if (t == -2) { writeString(lob.getFileName()); } } else { writeVarInt(small.length); write(small, 0, small.length); } } else { ValueLobDb lob = (ValueLobDb) v; byte[] small = lob.getSmall(); if (small == null) { writeVarInt(-3); writeVarInt(lob.getTableId()); writeVarLong(lob.getLobId()); writeVarLong(lob.getType().getPrecision()); } else { writeVarInt(small.length); write(small, 0, small.length); } } break; } case Value.ARRAY: case Value.ROW: { writeByte(type == Value.ARRAY ? ARRAY : ROW); Value[] list = ((ValueCollectionBase) v).getList(); writeVarInt(list.length); for (Value x : list) { writeValue(x); } break; } case Value.RESULT_SET: { writeByte(RESULT_SET); ResultInterface result = ((ValueResultSet) v).getResult(); result.reset(); int columnCount = result.getVisibleColumnCount(); writeVarInt(columnCount); for (int i = 0; i < columnCount; i++) { writeString(result.getAlias(i)); writeString(result.getColumnName(i)); TypeInfo columnType = result.getColumnType(i); writeVarInt(columnType.getValueType()); writeVarLong(columnType.getPrecision()); writeVarInt(columnType.getScale()); } while (result.next()) { writeByte((byte) 1); Value[] row = result.currentRow(); for (int i = 0; i < columnCount; i++) { writeValue(row[i]); } } writeByte((byte) 0); break; } case Value.INTERVAL_YEAR: case Value.INTERVAL_MONTH: case Value.INTERVAL_DAY: case Value.INTERVAL_HOUR: case Value.INTERVAL_MINUTE: { ValueInterval interval = (ValueInterval) v; int ordinal = type - Value.INTERVAL_YEAR; if (interval.isNegative()) { ordinal = ~ordinal; } writeByte(INTERVAL); writeByte((byte) ordinal); writeVarLong(interval.getLeading()); 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: { ValueInterval interval = (ValueInterval) v; int ordinal = type - Value.INTERVAL_YEAR; if (interval.isNegative()) { ordinal = ~ordinal; } writeByte(INTERVAL); writeByte((byte) ordinal); writeVarLong(interval.getLeading()); writeVarLong(interval.getRemaining()); break; } case Value.JSON: { writeByte((byte) JSON); byte[] b = v.getBytesNoCopy(); int len = b.length; writeVarInt(len); write(b, 0, len); break; } default: if (JdbcUtils.customDataTypesHandler != null) { byte[] b = v.getBytesNoCopy(); writeByte((byte) CUSTOM_DATA_TYPE); writeVarInt(type); writeVarInt(b.length); write(b, 0, b.length); break; } DbException.throwInternalError("type=" + v.getValueType()); } assert pos - start == getValueLen(v) : "value size error: got " + (pos - start) + " expected " + getValueLen(v); } /** * Read a value. * * @return the value */ public Value readValue() { int type = data[pos++] & 255; switch (type) { case NULL: return ValueNull.INSTANCE; case BOOLEAN_TRUE: return ValueBoolean.TRUE; case BOOLEAN_FALSE: return ValueBoolean.FALSE; case INT_NEG: return ValueInt.get(-readVarInt()); case ENUM: case INT: return ValueInt.get(readVarInt()); case LONG_NEG: return ValueLong.get(-readVarLong()); case Value.LONG: return ValueLong.get(readVarLong()); case BYTE: return ValueByte.get(readByte()); case SHORT: return ValueShort.get(readShortInt()); case DECIMAL_0_1: return (ValueDecimal) ValueDecimal.ZERO; case DECIMAL_0_1 + 1: return (ValueDecimal) ValueDecimal.ONE; case DECIMAL_SMALL_0: return ValueDecimal.get(BigDecimal.valueOf(readVarLong())); case DECIMAL_SMALL: { int scale = readVarInt(); return ValueDecimal.get(BigDecimal.valueOf(readVarLong(), scale)); } case DECIMAL: { int scale = readVarInt(); int len = readVarInt(); byte[] buff = Utils.newBytes(len); read(buff, 0, len); BigInteger b = new BigInteger(buff); return ValueDecimal.get(new BigDecimal(b, scale)); } case LOCAL_DATE: return ValueDate.fromDateValue(readVarLong()); case DATE: { long ms = readVarLong() * MILLIS_PER_MINUTE - zoneOffsetMillis; return ValueDate.fromDateValue(DateTimeUtils.dateValueFromLocalMillis( ms + DateTimeUtils.getTimeZoneOffsetMillis(ms))); } case LOCAL_TIME: return ValueTime.fromNanos(readVarLong() * 1_000_000 + readVarInt()); case TIME: { long ms = readVarLong() - zoneOffsetMillis; return ValueTime.fromNanos(DateTimeUtils.nanosFromLocalMillis( ms + DateTimeUtils.getTimeZoneOffsetMillis(ms))); } case TIME_TZ: return ValueTimeTimeZone.fromNanos(readVarInt() * DateTimeUtils.NANOS_PER_SECOND + readVarInt(), readTimeZone()); case LOCAL_TIMESTAMP: return ValueTimestamp.fromDateValueAndNanos(readVarLong(), readVarLong() * 1_000_000 + readVarInt()); case TIMESTAMP: return ValueTimestamp.fromMillis(readVarLong() - zoneOffsetMillis, readVarInt() % 1_000_000); case TIMESTAMP_TZ: { long dateValue = readVarLong(); long nanos = readVarLong(); int tz = readVarInt() * 60; return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, nanos, tz); } case TIMESTAMP_TZ_2: { long dateValue = readVarLong(); long nanos = readVarLong(); int tz = readTimeZone(); return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, nanos, tz); } case BYTES: { int len = readVarInt(); byte[] b = Utils.newBytes(len); read(b, 0, len); return ValueBytes.getNoCopy(b); } case GEOMETRY: { int len = readVarInt(); byte[] b = Utils.newBytes(len); read(b, 0, len); return ValueGeometry.get(b); } case JAVA_OBJECT: { int len = readVarInt(); byte[] b = Utils.newBytes(len); read(b, 0, len); return ValueJavaObject.getNoCopy(null, b, handler); } case UUID: return ValueUuid.get(readLong(), readLong()); case STRING: return ValueString.get(readString()); case STRING_IGNORECASE: return ValueStringIgnoreCase.get(readString()); case STRING_FIXED: return ValueStringFixed.get(readString()); case FLOAT_0_1: return ValueFloat.ZERO; case FLOAT_0_1 + 1: return ValueFloat.ONE; case DOUBLE_0_1: return ValueDouble.ZERO; case DOUBLE_0_1 + 1: return ValueDouble.ONE; case DOUBLE: return ValueDouble.get(Double.longBitsToDouble(Long.reverse(readVarLong()))); case FLOAT: return ValueFloat.get(Float.intBitsToFloat(Integer.reverse(readVarInt()))); case BLOB: case CLOB: { int smallLen = readVarInt(); if (smallLen >= 0) { byte[] small = Utils.newBytes(smallLen); read(small, 0, smallLen); return ValueLobDb.createSmallLob(type == BLOB ? Value.BLOB : Value.CLOB, small); } else if (smallLen == -3) { int tableId = readVarInt(); long lobId = readVarLong(); long precision = readVarLong(); return ValueLobDb.create(type == BLOB ? Value.BLOB : Value.CLOB, handler, tableId, lobId, null, precision); } else { int tableId = readVarInt(); int objectId = readVarInt(); long precision = 0; boolean compression = false; // -1: regular; -2: regular, but not linked (in this case: // including file name) if (smallLen == -1 || smallLen == -2) { precision = readVarLong(); compression = readByte() == 1; } if (smallLen == -2) { String filename = readString(); return ValueLob.openUnlinked(type == BLOB ? Value.BLOB : Value.CLOB, handler, tableId, objectId, precision, compression, filename); } return ValueLob.openLinked(type == BLOB ? Value.BLOB : Value.CLOB, handler, tableId, objectId, precision, compression); } } case ARRAY: case ROW: // Special storage type for ValueRow { int len = readVarInt(); Value[] list = new Value[len]; for (int i = 0; i < len; i++) { list[i] = readValue(); } return type == ARRAY ? ValueArray.get(list) : ValueRow.get(list); } case RESULT_SET: { SimpleResult rs = new SimpleResult(); int columns = readVarInt(); for (int i = 0; i < columns; i++) { rs.addColumn(readString(), readString(), readVarInt(), readVarLong(), readVarInt()); } while (readByte() != 0) { Value[] o = new Value[columns]; for (int i = 0; i < columns; i++) { o[i] = readValue(); } rs.addRow(o); } return ValueResultSet.get(rs); } case INTERVAL: { int ordinal = readByte(); boolean negative = ordinal < 0; if (negative) { ordinal = ~ordinal; } return ValueInterval.from(IntervalQualifier.valueOf(ordinal), negative, readVarLong(), ordinal < 5 ? 0 : readVarLong()); } case CUSTOM_DATA_TYPE: { if (JdbcUtils.customDataTypesHandler != null) { int customType = readVarInt(); int len = readVarInt(); byte[] b = Utils.newBytes(len); read(b, 0, len); return JdbcUtils.customDataTypesHandler.convert( ValueBytes.getNoCopy(b), customType); } throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, "No CustomDataTypesHandler has been set up"); } case JSON: { int len = readVarInt(); byte[] b = Utils.newBytes(len); read(b, 0, len); return ValueJson.getInternal(b); } default: if (type >= INT_0_15 && type < INT_0_15 + 16) { return ValueInt.get(type - INT_0_15); } else if (type >= LONG_0_7 && type < LONG_0_7 + 8) { return ValueLong.get(type - LONG_0_7); } else if (type >= BYTES_0_31 && type < BYTES_0_31 + 32) { int len = type - BYTES_0_31; byte[] b = Utils.newBytes(len); read(b, 0, len); return ValueBytes.getNoCopy(b); } else if (type >= STRING_0_31 && type < STRING_0_31 + 32) { return ValueString.get(readString(type - STRING_0_31)); } throw DbException.get(ErrorCode.FILE_CORRUPTED_1, "type: " + type); } } /** * Calculate the number of bytes required to encode the given value. * * @param v the value * @return the number of bytes required to store this value */ public int getValueLen(Value v) { return getValueLen(v, storeLocalTime); } /** * Calculate the number of bytes required to encode the given value. * * @param v the value * @param storeLocalTime * calculate size of DATE, TIME, and TIMESTAMP values with local * time storage format * @return the number of bytes required to store this value */ public static int getValueLen(Value v, boolean storeLocalTime) { if (v == ValueNull.INSTANCE) { return 1; } switch (v.getValueType()) { case Value.BOOLEAN: return 1; case Value.BYTE: return 2; case Value.SHORT: return 3; case Value.ENUM: case Value.INT: { int x = v.getInt(); if (x < 0) { return 1 + getVarIntLen(-x); } else if (x < 16) { return 1; } else { return 1 + getVarIntLen(x); } } case Value.LONG: { long x = v.getLong(); if (x < 0) { return 1 + getVarLongLen(-x); } else if (x < 8) { return 1; } else { return 1 + getVarLongLen(x); } } case Value.DOUBLE: { double x = v.getDouble(); if (x == 1.0d) { return 1; } long d = Double.doubleToLongBits(x); if (d == ValueDouble.ZERO_BITS) { return 1; } return 1 + getVarLongLen(Long.reverse(d)); } case Value.FLOAT: { float x = v.getFloat(); if (x == 1.0f) { return 1; } int f = Float.floatToIntBits(x); if (f == ValueFloat.ZERO_BITS) { return 1; } return 1 + getVarIntLen(Integer.reverse(f)); } case Value.STRING: { String s = v.getString(); int len = s.length(); if (len < 32) { return 1 + getStringWithoutLengthLen(s, len); } return 1 + getStringLen(s); } case Value.STRING_IGNORECASE: case Value.STRING_FIXED: return 1 + getStringLen(v.getString()); case Value.DECIMAL: { BigDecimal x = v.getBigDecimal(); if (BigDecimal.ZERO.equals(x)) { return 1; } else if (BigDecimal.ONE.equals(x)) { return 1; } int scale = x.scale(); BigInteger b = x.unscaledValue(); int bits = b.bitLength(); if (bits <= 63) { if (scale == 0) { return 1 + getVarLongLen(b.longValue()); } return 1 + getVarIntLen(scale) + getVarLongLen(b.longValue()); } byte[] bytes = b.toByteArray(); return 1 + getVarIntLen(scale) + getVarIntLen(bytes.length) + bytes.length; } case Value.TIME: if (storeLocalTime) { long nanos = ((ValueTime) v).getNanos(); long millis = nanos / 1_000_000; nanos -= millis * 1_000_000; return 1 + getVarLongLen(millis) + getVarLongLen(nanos); } return 1 + getVarLongLen(v.getTime(null).getTime() + zoneOffsetMillis); case Value.TIME_TZ: { ValueTimeTimeZone ts = (ValueTimeTimeZone) v; long nanosOfDay = ts.getNanos(); int tz = ts.getTimeZoneOffsetSeconds(); return 1 + getVarIntLen((int) (nanosOfDay / DateTimeUtils.NANOS_PER_SECOND)) + getVarIntLen((int) (nanosOfDay % DateTimeUtils.NANOS_PER_SECOND)) + getTimeZoneLen(tz); } case Value.DATE: { if (storeLocalTime) { long dateValue = ((ValueDate) v).getDateValue(); return 1 + getVarLongLen(dateValue); } long x = v.getDate(null).getTime() + zoneOffsetMillis; return 1 + getVarLongLen(x / MILLIS_PER_MINUTE); } case Value.TIMESTAMP: { if (storeLocalTime) { ValueTimestamp ts = (ValueTimestamp) v; long dateValue = ts.getDateValue(); long nanos = ts.getTimeNanos(); long millis = nanos / 1_000_000; nanos -= millis * 1_000_000; return 1 + getVarLongLen(dateValue) + getVarLongLen(millis) + getVarLongLen(nanos); } Timestamp ts = v.getTimestamp(null); return 1 + getVarLongLen(ts.getTime() + zoneOffsetMillis) + getVarIntLen(ts.getNanos() % 1_000_000); } case Value.TIMESTAMP_TZ: { ValueTimestampTimeZone ts = (ValueTimestampTimeZone) v; long dateValue = ts.getDateValue(); long nanos = ts.getTimeNanos(); int tz = ts.getTimeZoneOffsetSeconds(); return 1 + getVarLongLen(dateValue) + getVarLongLen(nanos) + (tz % 60 == 0 ? getVarIntLen(tz / 60) : getTimeZoneLen(tz)); } case Value.GEOMETRY: case Value.JAVA_OBJECT: { byte[] b = v.getBytesNoCopy(); return 1 + getVarIntLen(b.length) + b.length; } case Value.BYTES: { byte[] b = v.getBytesNoCopy(); int len = b.length; if (len < 32) { return 1 + b.length; } return 1 + getVarIntLen(b.length) + b.length; } case Value.UUID: return 1 + LENGTH_LONG + LENGTH_LONG; case Value.BLOB: case Value.CLOB: { int len = 1; if (v instanceof ValueLob) { ValueLob lob = (ValueLob) v; byte[] small = lob.getSmall(); if (small == null) { int t = -1; if (!lob.isLinkedToTable()) { t = -2; } len += getVarIntLen(t); len += getVarIntLen(lob.getTableId()); len += getVarIntLen(lob.getObjectId()); len += getVarLongLen(lob.getType().getPrecision()); len += 1; if (t == -2) { len += getStringLen(lob.getFileName()); } } else { len += getVarIntLen(small.length); len += small.length; } } else { ValueLobDb lob = (ValueLobDb) v; byte[] small = lob.getSmall(); if (small == null) { len += getVarIntLen(-3); len += getVarIntLen(lob.getTableId()); len += getVarLongLen(lob.getLobId()); len += getVarLongLen(lob.getType().getPrecision()); } else { len += getVarIntLen(small.length); len += small.length; } } return len; } case Value.ARRAY: case Value.ROW: { Value[] list = ((ValueCollectionBase) v).getList(); int len = 1 + getVarIntLen(list.length); for (Value x : list) { len += getValueLen(x, storeLocalTime); } return len; } case Value.RESULT_SET: { int len = 1; ResultInterface result = ((ValueResultSet) v).getResult(); int columnCount = result.getVisibleColumnCount(); len += getVarIntLen(columnCount); for (int i = 0; i < columnCount; i++) { len += getStringLen(result.getAlias(i)); len += getStringLen(result.getColumnName(i)); TypeInfo columnType = result.getColumnType(i); len += getVarIntLen(columnType.getValueType()); len += getVarLongLen(columnType.getPrecision()); len += getVarIntLen(columnType.getScale()); } while (result.next()) { len++; Value[] row = result.currentRow(); for (int i = 0; i < columnCount; i++) { Value val = row[i]; len += getValueLen(val, storeLocalTime); } } len++; return len; } case Value.INTERVAL_YEAR: case Value.INTERVAL_MONTH: case Value.INTERVAL_DAY: case Value.INTERVAL_HOUR: case Value.INTERVAL_MINUTE: { ValueInterval interval = (ValueInterval) v; return 2 + getVarLongLen(interval.getLeading()); } 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: { ValueInterval interval = (ValueInterval) v; return 2 + getVarLongLen(interval.getLeading()) + getVarLongLen(interval.getRemaining()); } case Value.JSON: { byte[] b = v.getBytesNoCopy(); return 1 + getVarIntLen(b.length) + b.length; } default: if (JdbcUtils.customDataTypesHandler != null) { byte[] b = v.getBytesNoCopy(); return 1 + getVarIntLen(v.getValueType()) + getVarIntLen(b.length) + b.length; } throw DbException.throwInternalError("type=" + v.getValueType()); } } /** * Set the current read / write position. * * @param pos the new position */ public void setPos(int pos) { this.pos = pos; } /** * Write a short integer at the current position. * The current position is incremented. * * @param x the value */ public void writeShortInt(int x) { byte[] buff = data; buff[pos++] = (byte) (x >> 8); buff[pos++] = (byte) x; } /** * Read an short integer at the current position. * The current position is incremented. * * @return the value */ public short readShortInt() { byte[] buff = data; return (short) (((buff[pos++] & 0xff) << 8) + (buff[pos++] & 0xff)); } /** * Shrink the array to this size. * * @param size the new size */ public void truncate(int size) { if (pos > size) { byte[] buff = Arrays.copyOf(data, size); this.pos = size; data = buff; } } /** * The number of bytes required for a variable size int. * * @param x the value * @return the len */ private static int getVarIntLen(int x) { if ((x & (-1 << 7)) == 0) { return 1; } else if ((x & (-1 << 14)) == 0) { return 2; } else if ((x & (-1 << 21)) == 0) { return 3; } else if ((x & (-1 << 28)) == 0) { return 4; } return 5; } /** * Write a variable size int. * * @param x the value */ public void writeVarInt(int x) { while ((x & ~0x7f) != 0) { data[pos++] = (byte) (x | 0x80); x >>>= 7; } data[pos++] = (byte) x; } /** * Read a variable size int. * * @return the value */ public int readVarInt() { int b = data[pos]; if (b >= 0) { pos++; return b; } // a separate function so that this one can be inlined return readVarIntRest(b); } private int readVarIntRest(int b) { int x = b & 0x7f; b = data[pos + 1]; if (b >= 0) { pos += 2; return x | (b << 7); } x |= (b & 0x7f) << 7; b = data[pos + 2]; if (b >= 0) { pos += 3; return x | (b << 14); } x |= (b & 0x7f) << 14; b = data[pos + 3]; if (b >= 0) { pos += 4; return x | b << 21; } x |= ((b & 0x7f) << 21) | (data[pos + 4] << 28); pos += 5; return x; } /** * The number of bytes required for a variable size long. * * @param x the value * @return the len */ public static int getVarLongLen(long x) { int i = 1; while (true) { x >>>= 7; if (x == 0) { return i; } i++; } } /** * Write a variable size long. * * @param x the value */ public void writeVarLong(long x) { while ((x & ~0x7f) != 0) { data[pos++] = (byte) (x | 0x80); x >>>= 7; } data[pos++] = (byte) x; } /** * Read a variable size long. * * @return the value */ public long readVarLong() { long x = data[pos++]; if (x >= 0) { return x; } x &= 0x7f; for (int s = 7;; s += 7) { long b = data[pos++]; x |= (b & 0x7f) << s; if (b >= 0) { return x; } } } private static int getTimeZoneLen(int timeZoneOffset) { if (timeZoneOffset % 900 == 0) { return 1; } else if (timeZoneOffset > 0) { return getVarIntLen(timeZoneOffset) + 1; } else { return getVarIntLen(-timeZoneOffset) + 1; } } private void writeTimeZone(int timeZoneOffset) { // Valid JSR-310 offsets are -64,800..64,800 // Use 1 byte for common time zones (including +8:45 etc.) if (timeZoneOffset % 900 == 0) { // -72..72 writeByte((byte) (timeZoneOffset / 900)); } else if (timeZoneOffset > 0) { writeByte(Byte.MAX_VALUE); writeVarInt(timeZoneOffset); } else { writeByte(Byte.MIN_VALUE); writeVarInt(-timeZoneOffset); } } private int readTimeZone() { byte x = data[pos++]; if (x == Byte.MAX_VALUE) { return readVarInt(); } else if (x == Byte.MIN_VALUE) { return -readVarInt(); } else { return x * 900; } } /** * Check if there is still enough capacity in the buffer. * This method extends the buffer if required. * * @param plus the number of additional bytes required */ public void checkCapacity(int plus) { if (pos + plus >= data.length) { // a separate method to simplify inlining expand(plus); } } private void expand(int plus) { // must copy everything, because pos could be 0 and data may be // still required data = Utils.copyBytes(data, (data.length + plus) * 2); } /** * Fill up the buffer with empty space and an (initially empty) checksum * until the size is a multiple of Constants.FILE_BLOCK_SIZE. */ public void fillAligned() { // 0..6 > 8, 7..14 > 16, 15..22 > 24, ... int len = MathUtils.roundUpInt(pos + 2, Constants.FILE_BLOCK_SIZE); pos = len; if (data.length < len) { checkCapacity(len - data.length); } } /** * Copy a String from a reader to an output stream. * * @param source the reader * @param target the output stream */ public static void copyString(Reader source, OutputStream target) throws IOException { char[] buff = new char[Constants.IO_BUFFER_SIZE]; Data d = new Data(null, new byte[3 * Constants.IO_BUFFER_SIZE], false); while (true) { int l = source.read(buff); if (l < 0) { break; } d.writeStringWithoutLength(buff, l); target.write(d.data, 0, d.pos); d.reset(); } } public DataHandler getHandler() { return handler; } /** * Reset the cached calendar for default timezone, for example after * changing the default timezone. */ public static void resetCalendar() { zoneOffsetMillis = new GregorianCalendar().get(Calendar.ZONE_OFFSET); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy