blob: ed6995d4fc9776a43106498d34ec9a1888db13f5 [file] [log] [blame]
/*
* 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();
}
}