| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.asterix.jdbc.core; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.math.BigDecimal; |
| import java.math.RoundingMode; |
| import java.nio.charset.StandardCharsets; |
| import java.sql.Date; |
| import java.sql.SQLException; |
| import java.sql.Time; |
| import java.sql.Timestamp; |
| import java.time.Duration; |
| import java.time.Instant; |
| import java.time.LocalDate; |
| import java.time.LocalDateTime; |
| import java.time.LocalTime; |
| import java.time.Period; |
| import java.time.ZoneId; |
| import java.time.format.DateTimeParseException; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.TimeZone; |
| import java.util.UUID; |
| import java.util.concurrent.TimeUnit; |
| |
| import com.fasterxml.jackson.core.JsonGenerator; |
| import com.fasterxml.jackson.core.JsonParser; |
| import com.fasterxml.jackson.core.JsonToken; |
| import com.fasterxml.jackson.databind.BeanDescription; |
| import com.fasterxml.jackson.databind.DeserializationConfig; |
| import com.fasterxml.jackson.databind.DeserializationContext; |
| import com.fasterxml.jackson.databind.DeserializationFeature; |
| import com.fasterxml.jackson.databind.JsonDeserializer; |
| import com.fasterxml.jackson.databind.ObjectMapper; |
| import com.fasterxml.jackson.databind.ObjectReader; |
| import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; |
| import com.fasterxml.jackson.databind.module.SimpleModule; |
| |
| public final class ADBRowStore { |
| |
| static final char TEXT_DELIMITER = ':'; |
| private static final String ROW_STORE_ATTR_NAME = ADBRowStore.class.getSimpleName(); |
| |
| private static final int FLOAT_NAN_BITS = Float.floatToIntBits(Float.NaN); |
| private static final int FLOAT_POSITIVE_ZERO_BITS = Float.floatToIntBits(+0.0f); |
| private static final int FLOAT_NEGATIVE_ZERO_BITS = Float.floatToIntBits(-0.0f); |
| private static final long DOUBLE_NAN_BITS = Double.doubleToLongBits(Double.NaN); |
| private static final long DOUBLE_POSITIVE_ZERO_BITS = Double.doubleToLongBits(+0.0d); |
| private static final long DOUBLE_NEGATIVE_ZERO_BITS = Double.doubleToLongBits(-0.0d); |
| |
| static final Map<Class<?>, GetObjectFunction> OBJECT_ACCESSORS_ATOMIC = createAtomicObjectAccessorMap(); |
| |
| static final List<Class<?>> GET_OBJECT_NON_ATOMIC = Arrays.asList(Collection.class, List.class, Map.class); |
| |
| private static final ZoneId TZ_UTC = ZoneId.of("UTC"); |
| |
| private final ADBResultSet resultSet; |
| |
| private final ADBDatatype[] columnTypes; |
| private final Object[] objectStore; |
| private final long[] registerStore; // 2 registers per column |
| |
| private final TimeZone tzSystem = TimeZone.getDefault(); |
| |
| private int parsedLength; |
| private long currentDateChronon; |
| private JsonGenerator jsonGen; |
| private StringWriter jsonGenBuffer; |
| |
| public ADBRowStore(ADBResultSet resultSet, int initialColumnCount) { |
| this.resultSet = Objects.requireNonNull(resultSet); |
| columnTypes = new ADBDatatype[initialColumnCount]; |
| objectStore = new Object[initialColumnCount]; |
| registerStore = new long[initialColumnCount * 2]; |
| } |
| |
| void reset() { |
| Arrays.fill(columnTypes, ADBDatatype.MISSING); |
| Arrays.fill(registerStore, 0); |
| Arrays.fill(objectStore, null); |
| } |
| |
| private void setColumnType(int columnIndex, ADBDatatype columnType) { |
| columnTypes[columnIndex] = columnType; |
| } |
| |
| ADBDatatype getColumnType(int columnIndex) { |
| return columnTypes[columnIndex]; |
| } |
| |
| void putColumn(int columnIndex, char[] textChars, int textOffset, int textLength) throws SQLException { |
| byte valueTypeTag = parseTypeTag(textChars, textOffset, textLength); |
| ADBDatatype valueType = ADBDatatype.findByTypeTag(valueTypeTag); |
| if (valueType == null) { |
| throw getErrorReporter().errorUnexpectedType(valueTypeTag); |
| } |
| |
| int nonTaggedOffset = textOffset + parsedLength; |
| int nonTaggedLength = textLength - parsedLength; |
| int nonTaggedEnd = nonTaggedOffset + nonTaggedLength; // = textOffset + textLength |
| |
| setColumnType(columnIndex, valueType); |
| |
| // NULL, BOOLEAN, BIGINT shouldn't normally happen. only handle here for completeness |
| |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| // no content |
| break; |
| case BOOLEAN: |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| case FLOAT: |
| case DOUBLE: |
| case DATE: |
| case TIME: |
| case DATETIME: |
| case YEARMONTHDURATION: |
| case DAYTIMEDURATION: |
| long r0 = parseInt64(textChars, nonTaggedOffset, nonTaggedEnd); |
| setColumnRegisters(columnIndex, r0, 0); |
| break; |
| case STRING: |
| objectStore[columnIndex] = new String(textChars, nonTaggedOffset, nonTaggedLength); |
| break; |
| case DURATION: |
| int delimiterOffset = indexOf(TEXT_DELIMITER, textChars, nonTaggedOffset, nonTaggedEnd); |
| if (delimiterOffset < 0 || delimiterOffset == nonTaggedEnd - 1) { |
| throw getErrorReporter().errorInProtocol(); |
| } |
| r0 = parseInt64(textChars, nonTaggedOffset, delimiterOffset); |
| long r1 = parseInt64(textChars, delimiterOffset + 1, nonTaggedEnd); |
| setColumnRegisters(columnIndex, r0, r1); |
| break; |
| case UUID: |
| // TODO: better encoding as 2 longs? |
| objectStore[columnIndex] = UUID.fromString(new String(textChars, nonTaggedOffset, nonTaggedLength)); |
| break; |
| case OBJECT: |
| case ARRAY: |
| case MULTISET: |
| // Unexpected (shouldn't be called) |
| throw new IllegalArgumentException(String.valueOf(valueType)); |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| void putNullColumn(int columnIndex) { |
| setColumnType(columnIndex, ADBDatatype.NULL); |
| } |
| |
| void putBooleanColumn(int columnIndex, boolean value) { |
| setColumnType(columnIndex, ADBDatatype.BOOLEAN); |
| setColumnRegisters(columnIndex, value ? 1 : 0, 0); |
| } |
| |
| void putInt64Column(int columnIndex, long value) { |
| setColumnType(columnIndex, ADBDatatype.BIGINT); |
| setColumnRegisters(columnIndex, value, 0); |
| } |
| |
| void putArrayColumn(int columnIndex, List<?> value) { |
| setColumnType(columnIndex, ADBDatatype.ARRAY); |
| objectStore[columnIndex] = Objects.requireNonNull(value); |
| } |
| |
| void putRecordColumn(int columnIndex, Map<?, ?> value) { |
| setColumnType(columnIndex, ADBDatatype.OBJECT); |
| objectStore[columnIndex] = Objects.requireNonNull(value); |
| } |
| |
| private void setColumnRegisters(int columnIndex, long r0, long r1) { |
| int registerPos = columnIndex * 2; |
| registerStore[registerPos] = r0; |
| registerStore[++registerPos] = r1; |
| } |
| |
| private long getColumnRegister(int columnIndex, int registerIndex) { |
| int registerPos = columnIndex * 2; |
| switch (registerIndex) { |
| case 0: |
| break; |
| case 1: |
| registerPos++; |
| break; |
| default: |
| throw new IllegalArgumentException(); |
| } |
| return registerStore[registerPos]; |
| } |
| |
| private boolean getColumnRegisterAsBoolean(int columnIndex, int registerIndex) { |
| return getColumnRegister(columnIndex, registerIndex) != 0; |
| } |
| |
| private byte getColumnRegisterAsByte(int columnIndex, int registerIndex) { |
| return (byte) getColumnRegister(columnIndex, registerIndex); |
| } |
| |
| private short getColumnRegisterAsShort(int columnIndex, int registerIndex) { |
| return (short) getColumnRegister(columnIndex, registerIndex); |
| } |
| |
| private int getColumnRegisterAsInt(int columnIndex, int registerIndex) { |
| return (int) getColumnRegister(columnIndex, registerIndex); |
| } |
| |
| private float getColumnRegisterAsFloat(int columnIndex, int registerIndex) { |
| return Float.intBitsToFloat(getColumnRegisterAsFloatBits(columnIndex, registerIndex)); |
| } |
| |
| private boolean isColumnRegisterZeroOrNanFloat(int columnIndex, int registerIndex) { |
| int bits = getColumnRegisterAsFloatBits(columnIndex, registerIndex); |
| return bits == FLOAT_POSITIVE_ZERO_BITS || bits == FLOAT_NEGATIVE_ZERO_BITS || bits == FLOAT_NAN_BITS; |
| } |
| |
| private int getColumnRegisterAsFloatBits(int columnIndex, int registerIndex) { |
| return getColumnRegisterAsInt(columnIndex, registerIndex); |
| } |
| |
| private double getColumnRegisterAsDouble(int columnIndex, int registerIndex) { |
| return Double.longBitsToDouble(getColumnRegisterAsDoubleBits(columnIndex, registerIndex)); |
| } |
| |
| private boolean isColumnRegisterZeroOrNanDouble(int columnIndex, int registerIndex) { |
| long bits = getColumnRegisterAsDoubleBits(columnIndex, registerIndex); |
| return bits == DOUBLE_POSITIVE_ZERO_BITS || bits == DOUBLE_NEGATIVE_ZERO_BITS || bits == DOUBLE_NAN_BITS; |
| } |
| |
| private long getColumnRegisterAsDoubleBits(int columnIndex, int registerIndex) { |
| return getColumnRegister(columnIndex, registerIndex); |
| } |
| |
| private Period getColumnRegisterAsPeriod(int columnIndex, int registerIndex) { |
| return Period.ofMonths((int) getColumnRegister(columnIndex, registerIndex)); |
| } |
| |
| private Duration getColumnRegisterAsDuration(int columnIndex, int registerIndex) { |
| return Duration.ofMillis((int) getColumnRegister(columnIndex, registerIndex)); |
| } |
| |
| private Number getNumberFromObjectStore(int columnIndex) { |
| Object o = objectStore[columnIndex]; |
| if (o != null) { |
| return (Number) o; |
| } |
| Number n; |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case TINYINT: |
| n = getColumnRegisterAsByte(columnIndex, 0); |
| break; |
| case SMALLINT: |
| n = getColumnRegisterAsShort(columnIndex, 0); |
| break; |
| case INTEGER: |
| n = getColumnRegisterAsInt(columnIndex, 0); |
| break; |
| case BIGINT: |
| n = getColumnRegister(columnIndex, 0); |
| break; |
| case FLOAT: |
| n = getColumnRegisterAsFloat(columnIndex, 0); |
| break; |
| case DOUBLE: |
| n = getColumnRegisterAsDouble(columnIndex, 0); |
| break; |
| default: |
| throw new IllegalArgumentException(String.valueOf(valueType)); |
| } |
| objectStore[columnIndex] = n; |
| return n; |
| } |
| |
| private String getStringFromObjectStore(int columnIndex) { |
| return (String) objectStore[columnIndex]; |
| } |
| |
| private UUID getUUIDFromObjectStore(int columnIndex) { |
| return (UUID) objectStore[columnIndex]; |
| } |
| |
| private Period getPeriodFromObjectStore(int columnIndex) { |
| Object o = objectStore[columnIndex]; |
| if (o != null) { |
| return (Period) o; |
| } |
| ADBDatatype valueType = getColumnType(columnIndex); |
| if (valueType != ADBDatatype.YEARMONTHDURATION) { |
| throw new IllegalArgumentException(String.valueOf(valueType)); |
| } |
| Period v = getColumnRegisterAsPeriod(columnIndex, 0); |
| objectStore[columnIndex] = v; |
| return v; |
| } |
| |
| private Duration getDurationFromObjectStore(int columnIndex) { |
| Object o = objectStore[columnIndex]; |
| if (o != null) { |
| return (Duration) o; |
| } |
| ADBDatatype valueType = getColumnType(columnIndex); |
| if (valueType != ADBDatatype.DAYTIMEDURATION) { |
| throw new IllegalArgumentException(String.valueOf(valueType)); |
| } |
| Duration v = getColumnRegisterAsDuration(columnIndex, 0); |
| objectStore[columnIndex] = v; |
| return v; |
| } |
| |
| private String getISODurationStringFromObjectStore(int columnIndex) { |
| Object o = objectStore[columnIndex]; |
| if (o != null) { |
| return (String) o; |
| } |
| ADBDatatype valueType = getColumnType(columnIndex); |
| if (valueType != ADBDatatype.DURATION) { |
| throw new IllegalArgumentException(String.valueOf(valueType)); |
| } |
| String v = getColumnRegisterAsPeriod(columnIndex, 0).toString() |
| + getColumnRegisterAsDuration(columnIndex, 1).toString().substring(1); |
| objectStore[columnIndex] = v; |
| return v; |
| } |
| |
| boolean getBoolean(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return false; |
| case BOOLEAN: |
| return getColumnRegisterAsBoolean(columnIndex, 0); |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| return getColumnRegister(columnIndex, 0) != 0; |
| case FLOAT: |
| return !isColumnRegisterZeroOrNanFloat(columnIndex, 0); |
| case DOUBLE: |
| return !isColumnRegisterZeroOrNanDouble(columnIndex, 0); |
| case STRING: |
| return Boolean.parseBoolean(getStringFromObjectStore(columnIndex)); |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| byte getByte(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return 0; |
| case BOOLEAN: |
| return (byte) (getColumnRegisterAsBoolean(columnIndex, 0) ? 1 : 0); |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| return getColumnRegisterAsByte(columnIndex, 0); |
| case FLOAT: |
| return (byte) getColumnRegisterAsFloat(columnIndex, 0); |
| case DOUBLE: |
| return (byte) getColumnRegisterAsDouble(columnIndex, 0); |
| case STRING: |
| return (byte) parseInt64(getStringFromObjectStore(columnIndex)); |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| short getShort(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return 0; |
| case BOOLEAN: |
| return (short) (getColumnRegisterAsBoolean(columnIndex, 0) ? 1 : 0); |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| return getColumnRegisterAsShort(columnIndex, 0); |
| case FLOAT: |
| return (short) getColumnRegisterAsFloat(columnIndex, 0); |
| case DOUBLE: |
| return (short) getColumnRegisterAsDouble(columnIndex, 0); |
| case STRING: |
| return (short) parseInt64(getStringFromObjectStore(columnIndex)); |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| int getInt(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return 0; |
| case BOOLEAN: |
| return getColumnRegisterAsBoolean(columnIndex, 0) ? 1 : 0; |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| case DATE: |
| case TIME: |
| case YEARMONTHDURATION: |
| return getColumnRegisterAsInt(columnIndex, 0); |
| case FLOAT: |
| return (int) getColumnRegisterAsFloat(columnIndex, 0); |
| case DOUBLE: |
| return (int) getColumnRegisterAsDouble(columnIndex, 0); |
| case STRING: |
| return (int) parseInt64(getStringFromObjectStore(columnIndex)); |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| long getLong(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return 0; |
| case BOOLEAN: |
| return getColumnRegisterAsBoolean(columnIndex, 0) ? 1 : 0; |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| case DATE: |
| case TIME: |
| case DATETIME: |
| case YEARMONTHDURATION: |
| case DAYTIMEDURATION: |
| return getColumnRegister(columnIndex, 0); |
| case FLOAT: |
| return (long) getColumnRegisterAsFloat(columnIndex, 0); |
| case DOUBLE: |
| return (long) getColumnRegisterAsDouble(columnIndex, 0); |
| case STRING: |
| return parseInt64(getStringFromObjectStore(columnIndex)); |
| default: |
| // TODO:support temporal types? |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| float getFloat(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return 0; |
| case BOOLEAN: |
| return getColumnRegisterAsBoolean(columnIndex, 0) ? 1f : 0f; |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| return getColumnRegister(columnIndex, 0); |
| case FLOAT: |
| return getColumnRegisterAsFloat(columnIndex, 0); |
| case DOUBLE: |
| return (float) getColumnRegisterAsDouble(columnIndex, 0); |
| case STRING: |
| try { |
| return Float.parseFloat(getStringFromObjectStore(columnIndex)); |
| } catch (NumberFormatException e) { |
| throw getErrorReporter().errorInvalidValueOfType(valueType); |
| } |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| double getDouble(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return 0; |
| case BOOLEAN: |
| return getColumnRegisterAsBoolean(columnIndex, 0) ? 1d : 0d; |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| return getColumnRegister(columnIndex, 0); |
| case FLOAT: |
| return getColumnRegisterAsFloat(columnIndex, 0); |
| case DOUBLE: |
| return getColumnRegisterAsDouble(columnIndex, 0); |
| case STRING: |
| try { |
| return Double.parseDouble(getStringFromObjectStore(columnIndex)); |
| } catch (NumberFormatException e) { |
| throw getErrorReporter().errorInvalidValueOfType(valueType); |
| } |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| BigDecimal getBigDecimal(int columnIndex) throws SQLException { |
| return getBigDecimal(columnIndex, false, 0); |
| } |
| |
| @SuppressWarnings("UnpredictableBigDecimalConstructorCall") |
| BigDecimal getBigDecimal(int columnIndex, boolean setScale, int scale) throws SQLException { |
| BigDecimal dec; |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case BOOLEAN: |
| dec = getColumnRegisterAsBoolean(columnIndex, 0) ? BigDecimal.ONE : BigDecimal.ZERO; |
| break; |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| case DATE: |
| case TIME: |
| case DATETIME: |
| case YEARMONTHDURATION: |
| case DAYTIMEDURATION: |
| dec = BigDecimal.valueOf(getColumnRegister(columnIndex, 0)); |
| break; |
| case FLOAT: |
| try { |
| dec = new BigDecimal(getColumnRegisterAsFloat(columnIndex, 0)); |
| } catch (NumberFormatException e) { |
| throw getErrorReporter().errorInvalidValueOfType(valueType); |
| } |
| break; |
| case DOUBLE: |
| try { |
| dec = new BigDecimal(getColumnRegisterAsDouble(columnIndex, 0)); |
| } catch (NumberFormatException e) { |
| throw getErrorReporter().errorInvalidValueOfType(valueType); |
| } |
| break; |
| case STRING: |
| try { |
| dec = new BigDecimal(getStringFromObjectStore(columnIndex)); |
| } catch (NumberFormatException e) { |
| throw getErrorReporter().errorInvalidValueOfType(valueType); |
| } |
| break; |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| |
| return setScale ? dec.setScale(scale, RoundingMode.DOWN) : dec; |
| } |
| |
| private Date getDate(int columnIndex) throws SQLException { |
| return getDate(columnIndex, null); |
| } |
| |
| Date getDate(int columnIndex, Calendar cal) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case DATE: |
| return toDateFromDateChronon(getColumnRegister(columnIndex, 0), getTimeZone(cal, tzSystem)); |
| case DATETIME: |
| return toDateFromDatetimeChronon(getColumnRegister(columnIndex, 0), getTimeZone(cal, tzSystem)); |
| case STRING: |
| try { |
| LocalDate d = LocalDate.parse(getStringFromObjectStore(columnIndex)); // TODO:review |
| return new Date(d.getYear() - 1900, d.getMonthValue() - 1, d.getDayOfMonth()); |
| } catch (DateTimeParseException e) { |
| throw getErrorReporter().errorInvalidValueOfType(valueType); |
| } |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| LocalDate getLocalDate(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case DATE: |
| return toLocalDateFromDateChronon(getColumnRegister(columnIndex, 0)); |
| case DATETIME: |
| return toLocalDateFromDatetimeChronon(getColumnRegister(columnIndex, 0)); |
| case STRING: |
| try { |
| return LocalDate.parse(getStringFromObjectStore(columnIndex)); // TODO:review |
| } catch (DateTimeParseException e) { |
| throw getErrorReporter().errorInvalidValueOfType(valueType); |
| } |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| private Time getTime(int columnIndex) throws SQLException { |
| return getTime(columnIndex, null); |
| } |
| |
| Time getTime(int columnIndex, Calendar cal) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case TIME: |
| return toTimeFromTimeChronon(getColumnRegister(columnIndex, 0), getTimeZone(cal, tzSystem)); |
| case DATETIME: |
| return toTimeFromDatetimeChronon(getColumnRegister(columnIndex, 0), getTimeZone(cal, tzSystem)); |
| case STRING: |
| try { |
| LocalTime t = LocalTime.parse(getStringFromObjectStore(columnIndex)); // TODO:review |
| return toTimeFromTimeChronon(TimeUnit.NANOSECONDS.toMillis(t.toNanoOfDay()), |
| getTimeZone(cal, tzSystem)); |
| } catch (DateTimeParseException e) { |
| throw getErrorReporter().errorInvalidValueOfType(valueType); |
| } |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| LocalTime getLocalTime(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case TIME: |
| return toLocalTimeFromTimeChronon(getColumnRegister(columnIndex, 0)); |
| case DATETIME: |
| return toLocalTimeFromDatetimeChronon(getColumnRegister(columnIndex, 0)); |
| case STRING: |
| try { |
| return LocalTime.parse(getStringFromObjectStore(columnIndex)); // TODO:review |
| } catch (DateTimeParseException e) { |
| throw getErrorReporter().errorInvalidValueOfType(valueType); |
| } |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| private Timestamp getTimestamp(int columnIndex) throws SQLException { |
| return getTimestamp(columnIndex, null); |
| } |
| |
| Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case DATE: |
| return toTimestampFromDateChronon(getColumnRegister(columnIndex, 0), getTimeZone(cal, tzSystem)); |
| case DATETIME: |
| return toTimestampFromDatetimeChronon(getColumnRegister(columnIndex, 0), getTimeZone(cal, tzSystem)); |
| case STRING: |
| try { |
| Instant i = Instant.parse(getStringFromObjectStore(columnIndex)); |
| long millis0 = TimeUnit.SECONDS.toMillis(i.getEpochSecond()); |
| long millis1 = TimeUnit.NANOSECONDS.toMillis(i.getNano()); |
| return new Timestamp(millis0 + millis1); |
| } catch (DateTimeParseException e) { |
| throw getErrorReporter().errorInvalidValueOfType(valueType); |
| } |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| LocalDateTime getLocalDateTime(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case DATE: |
| return toLocalDateTimeFromDateChronon(getColumnRegister(columnIndex, 0)); |
| case DATETIME: |
| return toLocalDateTimeFromDatetimeChronon(getColumnRegister(columnIndex, 0)); |
| case STRING: |
| try { |
| return LocalDateTime.parse(getStringFromObjectStore(columnIndex)); // TODO:review |
| } catch (DateTimeParseException e) { |
| throw getErrorReporter().errorInvalidValueOfType(valueType); |
| } |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| Period getPeriod(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case YEARMONTHDURATION: |
| return getPeriodFromObjectStore(columnIndex); |
| case DURATION: |
| return getColumnRegisterAsPeriod(columnIndex, 0); |
| case STRING: |
| try { |
| return Period.parse(getStringFromObjectStore(columnIndex)); // TODO:review |
| } catch (DateTimeParseException e) { |
| throw getErrorReporter().errorInvalidValueOfType(valueType); |
| } |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| Duration getDuration(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case DAYTIMEDURATION: |
| return getDurationFromObjectStore(columnIndex); |
| case DURATION: |
| return getColumnRegisterAsDuration(columnIndex, 1); |
| case STRING: |
| try { |
| return Duration.parse(getStringFromObjectStore(columnIndex)); // TODO:review |
| } catch (DateTimeParseException e) { |
| throw getErrorReporter().errorInvalidValueOfType(valueType); |
| } |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| byte[] getBinary(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case STRING: |
| return getStringFromObjectStore(columnIndex).getBytes(StandardCharsets.UTF_8); |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| UUID getUUID(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case UUID: |
| return getUUIDFromObjectStore(columnIndex); |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| String getString(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case BOOLEAN: |
| return Boolean.toString(getColumnRegisterAsBoolean(columnIndex, 0)); |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| return Long.toString(getColumnRegister(columnIndex, 0)); |
| case FLOAT: |
| return Float.toString(getColumnRegisterAsFloat(columnIndex, 0)); |
| case DOUBLE: |
| return Double.toString(getColumnRegisterAsDouble(columnIndex, 0)); |
| case DATE: |
| return toLocalDateFromDateChronon(getColumnRegister(columnIndex, 0)).toString(); // TODO:review |
| case TIME: |
| return toLocalTimeFromTimeChronon(getColumnRegister(columnIndex, 0)).toString(); // TODO:review |
| case DATETIME: |
| return toLocalDateTimeFromDatetimeChronon(getColumnRegister(columnIndex, 0)).toString(); // TODO:review |
| case YEARMONTHDURATION: |
| return getPeriodFromObjectStore(columnIndex).toString(); // TODO:review |
| case DAYTIMEDURATION: |
| return getDurationFromObjectStore(columnIndex).toString(); // TODO:review |
| case DURATION: |
| return getISODurationStringFromObjectStore(columnIndex); // TODO:review |
| case STRING: |
| return getStringFromObjectStore(columnIndex); |
| case UUID: |
| return getUUIDFromObjectStore(columnIndex).toString(); |
| case OBJECT: |
| case ARRAY: |
| return printAsJson(objectStore[columnIndex]); |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| Reader getCharacterStream(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case STRING: |
| return new StringReader(getStringFromObjectStore(columnIndex)); |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| InputStream getInputStream(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case STRING: |
| return new ByteArrayInputStream(getStringFromObjectStore(columnIndex).getBytes(StandardCharsets.UTF_8)); |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| Object getObject(int columnIndex) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| case BOOLEAN: |
| return getColumnRegisterAsBoolean(columnIndex, 0); |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| case FLOAT: |
| case DOUBLE: |
| return getNumberFromObjectStore(columnIndex); |
| case DATE: |
| return toDateFromDateChronon(getColumnRegister(columnIndex, 0), tzSystem); |
| case TIME: |
| return toTimeFromTimeChronon(getColumnRegister(columnIndex, 0), tzSystem); |
| case DATETIME: |
| return toTimestampFromDatetimeChronon(getColumnRegister(columnIndex, 0), tzSystem); |
| case YEARMONTHDURATION: |
| return getPeriodFromObjectStore(columnIndex); |
| case DAYTIMEDURATION: |
| return getDurationFromObjectStore(columnIndex); |
| case DURATION: |
| return getISODurationStringFromObjectStore(columnIndex); |
| case STRING: |
| return getStringFromObjectStore(columnIndex); |
| case UUID: |
| return getUUIDFromObjectStore(columnIndex); |
| case OBJECT: |
| case ARRAY: |
| return objectStore[columnIndex]; // TODO:how to make immutable? |
| default: |
| throw getErrorReporter().errorUnexpectedType(valueType); |
| } |
| } |
| |
| <T> T getObject(int columnIndex, Class<T> targetType) throws SQLException { |
| ADBDatatype valueType = getColumnType(columnIndex); |
| switch (valueType) { |
| case MISSING: |
| case NULL: |
| return null; |
| default: |
| GetObjectFunction getter = OBJECT_ACCESSORS_ATOMIC.get(targetType); |
| Object v; |
| if (getter != null) { |
| v = getter.getObject(this, columnIndex); |
| } else if (GET_OBJECT_NON_ATOMIC.contains(targetType)) { |
| v = getObject(columnIndex); |
| } else { |
| throw getErrorReporter().errorUnexpectedType(targetType); |
| } |
| return targetType.cast(v); |
| } |
| } |
| |
| interface GetObjectFunction { |
| Object getObject(ADBRowStore rowStore, int columnIndex) throws SQLException; |
| } |
| |
| private static Map<Class<?>, GetObjectFunction> createAtomicObjectAccessorMap() { |
| Map<Class<?>, GetObjectFunction> map = new HashMap<>(); |
| map.put(Boolean.TYPE, ADBRowStore::getBoolean); |
| map.put(Boolean.class, ADBRowStore::getBoolean); |
| map.put(Byte.TYPE, ADBRowStore::getByte); |
| map.put(Byte.class, ADBRowStore::getByte); |
| map.put(Short.TYPE, ADBRowStore::getShort); |
| map.put(Short.class, ADBRowStore::getShort); |
| map.put(Integer.TYPE, ADBRowStore::getInt); |
| map.put(Integer.class, ADBRowStore::getInt); |
| map.put(Long.TYPE, ADBRowStore::getLong); |
| map.put(Long.class, ADBRowStore::getLong); |
| map.put(Float.TYPE, ADBRowStore::getFloat); |
| map.put(Float.class, ADBRowStore::getFloat); |
| map.put(Double.TYPE, ADBRowStore::getDouble); |
| map.put(Double.class, ADBRowStore::getDouble); |
| map.put(BigDecimal.class, ADBRowStore::getBigDecimal); |
| map.put(Date.class, ADBRowStore::getDate); |
| map.put(LocalDate.class, ADBRowStore::getLocalDate); |
| map.put(Time.class, ADBRowStore::getTime); |
| map.put(LocalTime.class, ADBRowStore::getLocalTime); |
| map.put(Timestamp.class, ADBRowStore::getTimestamp); |
| map.put(LocalDateTime.class, ADBRowStore::getLocalDateTime); |
| map.put(Period.class, ADBRowStore::getPeriod); |
| map.put(Duration.class, ADBRowStore::getDuration); |
| map.put(UUID.class, ADBRowStore::getUUID); |
| map.put(String.class, ADBRowStore::getString); |
| return map; |
| } |
| |
| private Date toDateFromDateChronon(long dateChrononInDays, TimeZone tz) { |
| return new Date(getDatetimeChrononAdjusted(TimeUnit.DAYS.toMillis(dateChrononInDays), tz)); |
| } |
| |
| private Date toDateFromDatetimeChronon(long datetimeChrononInMillis, TimeZone tz) { |
| return new Date(getDatetimeChrononAdjusted(datetimeChrononInMillis, tz)); |
| } |
| |
| private LocalDate toLocalDateFromDateChronon(long dateChrononInDays) { |
| return LocalDate.ofEpochDay(dateChrononInDays); |
| } |
| |
| private LocalDate toLocalDateFromDatetimeChronon(long datetimeChrononInMillis) { |
| return LocalDate.ofEpochDay(TimeUnit.MILLISECONDS.toDays(datetimeChrononInMillis)); |
| } |
| |
| private Time toTimeFromTimeChronon(long timeChrononInMillis, TimeZone tz) { |
| long datetimeChrononInMillis = getCurrentDateChrononInMillis() + timeChrononInMillis; |
| return new Time(getDatetimeChrononAdjusted(datetimeChrononInMillis, tz)); |
| } |
| |
| private Time toTimeFromDatetimeChronon(long datetimeChrononInMillis, TimeZone tz) { |
| return new Time(getDatetimeChrononAdjusted(datetimeChrononInMillis, tz)); |
| } |
| |
| private LocalTime toLocalTimeFromTimeChronon(long timeChrononInMillis) { |
| return LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(timeChrononInMillis)); |
| } |
| |
| private LocalTime toLocalTimeFromDatetimeChronon(long datetimeChrononInMillis) { |
| return LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(datetimeChrononInMillis)); |
| } |
| |
| private Timestamp toTimestampFromDatetimeChronon(long datetimeChrononInMillis, TimeZone tz) { |
| return new Timestamp(getDatetimeChrononAdjusted(datetimeChrononInMillis, tz)); |
| } |
| |
| private Timestamp toTimestampFromDateChronon(long dateChrononInDays, TimeZone tz) { |
| return new Timestamp(getDatetimeChrononAdjusted(TimeUnit.DAYS.toMillis(dateChrononInDays), tz)); |
| } |
| |
| private LocalDateTime toLocalDateTimeFromDatetimeChronon(long datetimeChrononInMillis) { |
| return LocalDateTime.ofInstant(Instant.ofEpochMilli(datetimeChrononInMillis), TZ_UTC); |
| } |
| |
| private LocalDateTime toLocalDateTimeFromDateChronon(long dateChrononInDays) { |
| return LocalDate.ofEpochDay(dateChrononInDays).atStartOfDay(); |
| } |
| |
| private long getDatetimeChrononAdjusted(long datetimeChrononInMillis, TimeZone tz) { |
| int tzOffset = tz.getOffset(datetimeChrononInMillis); |
| return datetimeChrononInMillis - tzOffset; |
| } |
| |
| private long getCurrentDateChrononInMillis() { |
| if (currentDateChronon == 0) { |
| currentDateChronon = TimeUnit.DAYS.toMillis(TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis())); |
| } |
| return currentDateChronon; |
| } |
| |
| private TimeZone getTimeZone(Calendar cal, TimeZone tzDefault) { |
| return cal != null ? cal.getTimeZone() : tzDefault; |
| } |
| |
| private String printAsJson(Object value) throws SQLException { |
| if (jsonGenBuffer == null) { |
| jsonGenBuffer = new StringWriter(); |
| try { |
| //TODO:FIXME:need to configure generator to print java.sql.Date/Times properly |
| jsonGen = resultSet.metadata.statement.connection.protocol.getDriverContext().getGenericObjectWriter() |
| .getFactory().createGenerator(jsonGenBuffer); |
| } catch (IOException e) { |
| throw getErrorReporter().errorInResultHandling(e); |
| } |
| } |
| try { |
| jsonGen.writeObject(value); |
| jsonGen.flush(); |
| return jsonGenBuffer.getBuffer().toString(); |
| } catch (IOException e) { |
| throw getErrorReporter().errorInResultHandling(e); |
| } finally { |
| jsonGenBuffer.getBuffer().setLength(0); |
| } |
| } |
| |
| ObjectReader createComplexColumnObjectReader(ObjectReader templateReader) { |
| return templateReader.withAttribute(ROW_STORE_ATTR_NAME, this); |
| } |
| |
| static void configureADMFormatDeserialization(ObjectMapper objectMapper, SimpleModule serdeModule) { |
| objectMapper.configure(DeserializationFeature.USE_LONG_FOR_INTS, true); |
| serdeModule.setDeserializerModifier(createADMFormatDeserializerModifier()); |
| } |
| |
| private static BeanDeserializerModifier createADMFormatDeserializerModifier() { |
| return new BeanDeserializerModifier() { |
| @Override |
| public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, |
| JsonDeserializer<?> deserializer) { |
| if (String.class.equals(beanDesc.getClassInfo().getAnnotated())) { |
| ADBRowStore rowStore = (ADBRowStore) config.getAttributes().getAttribute(ROW_STORE_ATTR_NAME); |
| return rowStore.createADMFormatStringDeserializer(); |
| } else { |
| return deserializer; |
| } |
| } |
| }; |
| } |
| |
| private JsonDeserializer<?> createADMFormatStringDeserializer() { |
| return new JsonDeserializer<Object>() { |
| @Override |
| public Object deserialize(JsonParser parser, DeserializationContext ctx) throws IOException { |
| if (!parser.hasToken(JsonToken.VALUE_STRING)) { |
| throw new IOException("Unexpected token"); |
| } |
| try { |
| ADBRowStore.this.reset(); |
| ADBRowStore.this.putColumn(0, parser.getTextCharacters(), parser.getTextOffset(), |
| parser.getTextLength()); |
| return ADBRowStore.this.getObject(0); |
| } catch (SQLException e) { |
| throw new IOException(e); |
| } |
| } |
| }; |
| } |
| |
| @FunctionalInterface |
| public interface ICharAccessor<T> { |
| char charAt(T input, int index); |
| } |
| |
| private long parseInt64(CharSequence buffer) throws SQLException { |
| return parseInt64(buffer, 0, buffer.length(), CharSequence::charAt); |
| } |
| |
| private long parseInt64(char[] buffer, int begin, int end) throws SQLException { |
| return parseInt64(buffer, begin, end, (input, index) -> input[index]); |
| } |
| |
| private <T> long parseInt64(T buffer, int begin, int end, ICharAccessor<T> charAccessor) throws SQLException { |
| if (end < begin) { |
| throw new IllegalArgumentException(); |
| } |
| boolean positive = true; |
| long value = 0; |
| int offset = begin; |
| char c = charAccessor.charAt(buffer, offset); |
| if (c == '+') { |
| offset++; |
| } else if (c == '-') { |
| offset++; |
| positive = false; |
| } |
| try { |
| for (; offset < end; offset++) { |
| c = charAccessor.charAt(buffer, offset); |
| if (c >= '0' && c <= '9') { |
| value = Math.addExact(Math.multiplyExact(value, 10L), '0' - c); |
| } else { |
| throw getErrorReporter().errorInProtocol(String.valueOf(c)); |
| } |
| } |
| if (positive) { |
| value = Math.multiplyExact(value, -1L); |
| } |
| return value; |
| } catch (ArithmeticException e) { |
| throw getErrorReporter().errorInProtocol(); |
| } |
| } |
| |
| private byte parseTypeTag(char[] textChars, int textOffset, int textLength) throws SQLException { |
| if (textLength == 0) { |
| // empty string |
| parsedLength = 0; |
| return ADBDatatype.STRING.getTypeTag(); |
| } |
| if (textChars[textOffset] == TEXT_DELIMITER) { |
| // any string |
| parsedLength = 1; |
| return ADBDatatype.STRING.getTypeTag(); |
| } |
| // any type |
| int typeTagLength = 2; |
| if (textLength < typeTagLength) { |
| throw getErrorReporter().errorInProtocol(); |
| } |
| byte parsedTypeTag = getByteFromValidHexChars(textChars[textOffset], textChars[textOffset + 1]); |
| if (parsedTypeTag == ADBDatatype.MISSING.getTypeTag() || parsedTypeTag == ADBDatatype.NULL.getTypeTag()) { |
| parsedLength = typeTagLength; |
| return parsedTypeTag; |
| } |
| int delimiterLength = 1; |
| if (textLength < typeTagLength + delimiterLength) { |
| throw getErrorReporter().errorInProtocol(); |
| } |
| if (textChars[textOffset + typeTagLength] != TEXT_DELIMITER) { |
| throw getErrorReporter().errorInProtocol(); |
| } |
| parsedLength = typeTagLength + delimiterLength; |
| return parsedTypeTag; |
| } |
| |
| private byte getByteFromValidHexChars(char c0, char c1) throws SQLException { |
| return (byte) ((getValueFromValidHexChar(c0) << 4) + getValueFromValidHexChar(c1)); |
| } |
| |
| private int getValueFromValidHexChar(char c) throws SQLException { |
| if (c >= '0' && c <= '9') { |
| return c - '0'; |
| } |
| if (c >= 'a' && c <= 'f') { |
| return 10 + c - 'a'; |
| } |
| if (c >= 'A' && c <= 'F') { |
| return 10 + c - 'A'; |
| } |
| throw getErrorReporter().errorInProtocol(String.valueOf(c)); |
| } |
| |
| private static int indexOf(char c, char[] array, int begin, int end) { |
| for (int i = begin; i < end; i++) { |
| if (array[i] == c) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| private ADBErrorReporter getErrorReporter() { |
| return resultSet.getErrorReporter(); |
| } |
| } |