From 6e6e5cd40c5157bc56fddded103d63846e9451e1 Mon Sep 17 00:00:00 2001 From: Piotr Fus Date: Wed, 3 Jan 2024 10:52:31 +0100 Subject: [PATCH] SNOW-974576 Extract type converters for JSON results (#1587) * SNOW-974576 Extract type converters for JSON result Co-authored-by: Przemyslaw Motacki --- .../client/core/SFFixedViewResultSet.java | 19 + .../client/core/SFJsonResultSet.java | 579 ++---------------- .../snowflake/client/core/SFResultSet.java | 44 +- .../client/core/json/BooleanConverter.java | 37 ++ .../client/core/json/BytesConverter.java | 68 ++ .../client/core/json/Converters.java | 74 +++ .../client/core/json/DateTimeConverter.java | 195 ++++++ .../client/core/json/NumberConverter.java | 192 ++++++ .../client/core/json/StringConverter.java | 155 +++++ .../snowflake/client/jdbc/RestRequest.java | 9 +- .../SnowflakeResultSetSerializableV1.java | 4 +- .../core/json/BooleanConverterTest.java | 51 ++ .../client/core/json/BytesConverterTest.java | 51 ++ .../core/json/DateTimeConverterTest.java | 147 +++++ .../client/core/json/NumberConverterTest.java | 163 +++++ .../client/core/json/StringConverterTest.java | 108 ++++ .../client/jdbc/MockConnectionTest.java | 18 + 17 files changed, 1367 insertions(+), 547 deletions(-) create mode 100644 src/main/java/net/snowflake/client/core/json/BooleanConverter.java create mode 100644 src/main/java/net/snowflake/client/core/json/BytesConverter.java create mode 100644 src/main/java/net/snowflake/client/core/json/Converters.java create mode 100644 src/main/java/net/snowflake/client/core/json/DateTimeConverter.java create mode 100644 src/main/java/net/snowflake/client/core/json/NumberConverter.java create mode 100644 src/main/java/net/snowflake/client/core/json/StringConverter.java create mode 100644 src/test/java/net/snowflake/client/core/json/BooleanConverterTest.java create mode 100644 src/test/java/net/snowflake/client/core/json/BytesConverterTest.java create mode 100644 src/test/java/net/snowflake/client/core/json/DateTimeConverterTest.java create mode 100644 src/test/java/net/snowflake/client/core/json/NumberConverterTest.java create mode 100644 src/test/java/net/snowflake/client/core/json/StringConverterTest.java diff --git a/src/main/java/net/snowflake/client/core/SFFixedViewResultSet.java b/src/main/java/net/snowflake/client/core/SFFixedViewResultSet.java index c4145649e..671857b93 100644 --- a/src/main/java/net/snowflake/client/core/SFFixedViewResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFFixedViewResultSet.java @@ -6,16 +6,19 @@ import java.sql.SQLException; import java.util.List; +import net.snowflake.client.core.json.Converters; import net.snowflake.client.jdbc.*; import net.snowflake.client.jdbc.SFBaseFileTransferAgent.CommandType; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; +import net.snowflake.common.core.SFBinaryFormat; import net.snowflake.common.core.SqlState; /** * Fixed view result set. This class iterates through any fixed view implementation and return the * objects as rows */ +// Works only for strings, numbers, etc, does not work for timestamps, dates, times etc. public class SFFixedViewResultSet extends SFJsonResultSet { private static final SFLogger logger = SFLoggerFactory.getLogger(SFFixedViewResultSet.class); @@ -26,6 +29,22 @@ public class SFFixedViewResultSet extends SFJsonResultSet { public SFFixedViewResultSet(SnowflakeFixedView fixedView, CommandType commandType, String queryID) throws SnowflakeSQLException { + super( + null, + new Converters( + null, + new SFSession(), + 0, + false, + false, + false, + false, + SFBinaryFormat.BASE64, + null, + null, + null, + null, + null)); this.fixedView = fixedView; this.commandType = commandType; this.queryID = queryID; diff --git a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java index a896388cf..95a220560 100644 --- a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java @@ -5,35 +5,27 @@ package net.snowflake.client.core; import java.math.BigDecimal; -import java.math.RoundingMode; -import java.nio.ByteBuffer; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.util.TimeZone; -import net.snowflake.client.core.arrow.ArrowResultUtil; -import net.snowflake.client.jdbc.*; -import net.snowflake.client.log.ArgSupplier; +import net.snowflake.client.core.json.Converters; +import net.snowflake.client.jdbc.ErrorCode; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; -import net.snowflake.common.core.SFBinary; -import net.snowflake.common.core.SFBinaryFormat; -import net.snowflake.common.core.SFTime; -import net.snowflake.common.core.SFTimestamp; -import org.apache.arrow.vector.Float8Vector; /** Abstract class used to represent snowflake result set in json format */ public abstract class SFJsonResultSet extends SFBaseResultSet { private static final SFLogger logger = SFLoggerFactory.getLogger(SFJsonResultSet.class); - TimeZone sessionTimeZone; + protected final TimeZone sessionTimeZone; + protected final Converters converters; - // Precision of maximum long value in Java (2^63-1). Precision is 19 - private static final int LONG_PRECISION = 19; - - private static final BigDecimal MAX_LONG_VAL = new BigDecimal(Long.MAX_VALUE); - private static final BigDecimal MIN_LONG_VAL = new BigDecimal(Long.MIN_VALUE); + protected SFJsonResultSet(TimeZone sessionTimeZone, Converters converters) { + this.sessionTimeZone = sessionTimeZone; + this.converters = converters; + } /** * Given a column index, get current row's value as an object @@ -100,570 +92,121 @@ public Object getObject(int columnIndex) throws SFException { * @throws SFException */ private Object getBigInt(int columnIndex, Object obj) throws SFException { - // If precision is < precision of max long precision, we can automatically convert to long. - // Otherwise, do a check to ensure it doesn't overflow max long value. - String numberAsString = obj.toString(); - if (numberAsString.length() >= LONG_PRECISION) { - BigDecimal bigNum = getBigDecimal(columnIndex); - if (bigNum.compareTo(MAX_LONG_VAL) == 1 || bigNum.compareTo(MIN_LONG_VAL) == -1) { - return bigNum; - } - } - return getLong(columnIndex); + return converters.getNumberConverter().getBigInt(obj, columnIndex); } @Override public String getString(int columnIndex) throws SFException { logger.debug("public String getString(int columnIndex)", false); - - // Column index starts from 1, not 0. Object obj = getObjectInternal(columnIndex); - if (obj == null) { - return null; - } - - // print timestamp in string format int columnType = resultSetMetaData.getInternalColumnType(columnIndex); - switch (columnType) { - case Types.BOOLEAN: - return ResultUtil.getBooleanAsString(ResultUtil.getBoolean(obj.toString())); - - case Types.TIMESTAMP: - case SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ: - case SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ: - SFTimestamp sfTS = getSFTimestamp(columnIndex); - int columnScale = resultSetMetaData.getScale(columnIndex); - - String timestampStr = - ResultUtil.getSFTimestampAsString( - sfTS, - columnType, - columnScale, - timestampNTZFormatter, - timestampLTZFormatter, - timestampTZFormatter, - session); - - logger.debug( - "Converting timestamp to string from: {} to: {}", - (ArgSupplier) obj::toString, - timestampStr); - - return timestampStr; - - case Types.DATE: - Date date = getDate(columnIndex); - - if (dateFormatter == null) { - throw new SFException(ErrorCode.INTERNAL_ERROR, "missing date formatter"); - } - - String dateStr = ResultUtil.getDateAsString(date, dateFormatter); - - logger.debug( - "Converting date to string from: {} to: {}", (ArgSupplier) obj::toString, dateStr); - - return dateStr; - - case Types.TIME: - SFTime sfTime = getSFTime(columnIndex); - - if (timeFormatter == null) { - throw new SFException(ErrorCode.INTERNAL_ERROR, "missing time formatter"); - } - - int scale = resultSetMetaData.getScale(columnIndex); - String timeStr = ResultUtil.getSFTimeAsString(sfTime, scale, timeFormatter); - - logger.debug( - "Converting time to string from: {} to: {}", (ArgSupplier) obj::toString, timeStr); - - return timeStr; - - case Types.BINARY: - if (binaryFormatter == null) { - throw new SFException(ErrorCode.INTERNAL_ERROR, "missing binary formatter"); - } - - if (binaryFormatter == SFBinaryFormat.HEX) { - // Shortcut: the values are already passed with hex encoding, so just - // return the string unchanged rather than constructing an SFBinary. - return obj.toString(); - } - - SFBinary sfb = new SFBinary(getBytes(columnIndex)); - return binaryFormatter.format(sfb); - - default: - break; - } - - return obj.toString(); + int columnSubType = resultSetMetaData.getInternalColumnType(columnIndex); + int scale = resultSetMetaData.getScale(columnIndex); + return converters.getStringConverter().getString(obj, columnType, columnSubType, scale); } @Override public boolean getBoolean(int columnIndex) throws SFException { logger.debug("public boolean getBoolean(int columnIndex)", false); - Object obj = getObjectInternal(columnIndex); - if (obj == null) { - return false; - } - if (obj instanceof Boolean) { - return (Boolean) obj; - } int columnType = resultSetMetaData.getColumnType(columnIndex); - // if type is an approved type that can be converted to Boolean, do this - if (columnType == Types.BOOLEAN - || columnType == Types.INTEGER - || columnType == Types.SMALLINT - || columnType == Types.TINYINT - || columnType == Types.BIGINT - || columnType == Types.BIT - || columnType == Types.VARCHAR - || columnType == Types.CHAR) { - String type = obj.toString(); - if ("1".equals(type) || Boolean.TRUE.toString().equalsIgnoreCase(type)) { - return true; - } - if ("0".equals(type) || Boolean.FALSE.toString().equalsIgnoreCase(type)) { - return false; - } - } - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.BOOLEAN_STR, obj); + return converters.getBooleanConverter().getBoolean(getObjectInternal(columnIndex), columnType); } @Override public byte getByte(int columnIndex) throws SFException { logger.debug("public short getByte(int columnIndex)", false); - - // Column index starts from 1, not 0. Object obj = getObjectInternal(columnIndex); - - if (obj == null) { - return 0; - } - - if (obj instanceof String) { - return Byte.parseByte((String) obj); - } else { - return ((Number) obj).byteValue(); - } + return converters.getNumberConverter().getByte(obj); } @Override public short getShort(int columnIndex) throws SFException { logger.debug("public short getShort(int columnIndex)", false); - - // Column index starts from 1, not 0. Object obj = getObjectInternal(columnIndex); - - if (obj == null) { - return 0; - } int columnType = resultSetMetaData.getColumnType(columnIndex); - try { - - if (obj instanceof String) { - String objString = (String) obj; - if (objString.contains(".") && (columnType == Types.FLOAT || columnType == Types.DOUBLE)) { - objString = objString.substring(0, objString.indexOf(".")); - } - return Short.parseShort(objString); - } else { - return ((Number) obj).shortValue(); - } - } catch (NumberFormatException ex) { - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.SHORT_STR, obj); - } + return converters.getNumberConverter().getShort(obj, columnType); } @Override public int getInt(int columnIndex) throws SFException { logger.debug("public int getInt(int columnIndex)", false); - - // Column index starts from 1, not 0. Object obj = getObjectInternal(columnIndex); - - if (obj == null) { - return 0; - } int columnType = resultSetMetaData.getColumnType(columnIndex); - try { - if (obj instanceof String) { - String objString = (String) obj; - if (objString.contains(".") && (columnType == Types.FLOAT || columnType == Types.DOUBLE)) { - objString = objString.substring(0, objString.indexOf(".")); - } - return Integer.parseInt(objString); - } else { - return ((Number) obj).intValue(); - } - } catch (NumberFormatException ex) { - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.INT_STR, obj); - } + return converters.getNumberConverter().getInt(obj, columnType); } @Override public long getLong(int columnIndex) throws SFException { logger.debug("public long getLong(int columnIndex)", false); - - // Column index starts from 1, not 0. Object obj = getObjectInternal(columnIndex); - - if (obj == null) { - return 0; - } int columnType = resultSetMetaData.getColumnType(columnIndex); - try { - if (obj instanceof String) { - String objString = (String) obj; - if (objString.contains(".") && (columnType == Types.FLOAT || columnType == Types.DOUBLE)) { - objString = objString.substring(0, objString.indexOf(".")); - } - return Long.parseLong(objString); - } else { - return ((Number) obj).longValue(); - } - } catch (NumberFormatException nfe) { - - if (Types.INTEGER == columnType || Types.SMALLINT == columnType) { - throw new SFException( - ErrorCode.INTERNAL_ERROR, SnowflakeUtil.LONG_STR + ": " + obj.toString()); - } else { - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.LONG_STR, obj); - } - } + return converters.getNumberConverter().getLong(obj, columnType); } @Override public BigDecimal getBigDecimal(int columnIndex) throws SFException { logger.debug("public BigDecimal getBigDecimal(int columnIndex)", false); - - // Column index starts from 1, not 0. Object obj = getObjectInternal(columnIndex); - - if (obj == null) { - return null; - } int columnType = resultSetMetaData.getColumnType(columnIndex); - try { - if (columnType != Types.TIME - && columnType != Types.TIMESTAMP - && columnType != Types.TIMESTAMP_WITH_TIMEZONE) { - return new BigDecimal(obj.toString()); - } - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.BIG_DECIMAL_STR, obj); - - } catch (NumberFormatException ex) { - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.BIG_DECIMAL_STR, obj); - } + return converters.getNumberConverter().getBigDecimal(obj, columnType); } @Override public BigDecimal getBigDecimal(int columnIndex, int scale) throws SFException { logger.debug("public BigDecimal getBigDecimal(int columnIndex)", false); - Object obj = getObjectInternal(columnIndex); - - if (obj == null) { - return null; - } - BigDecimal value = new BigDecimal(obj.toString()); - - value = value.setScale(scale, RoundingMode.HALF_UP); - - return value; - } - - private TimeZone adjustTimezoneForTimestampTZ(int columnIndex) throws SFException { - // If the timestamp is of type timestamp_tz, use the associated offset timezone instead of the - // session timezone for formatting - Object obj = getObjectInternal(columnIndex); - int subType = resultSetMetaData.getInternalColumnType(columnIndex); - if (obj != null && subType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ && resultVersion > 0) { - String timestampStr = obj.toString(); - int indexForSeparator = timestampStr.indexOf(' '); - String timezoneIndexStr = timestampStr.substring(indexForSeparator + 1); - return SFTimestamp.convertTimezoneIndexToTimeZone(Integer.parseInt(timezoneIndexStr)); - } - // By default, return session timezone - return sessionTimeZone; - } - - private SFTimestamp getSFTimestamp(int columnIndex) throws SFException { - logger.debug("public Timestamp getTimestamp(int columnIndex)", false); - - Object obj = getObjectInternal(columnIndex); - - if (obj == null) { - return null; - } - - return ResultUtil.getSFTimestamp( - obj.toString(), - resultSetMetaData.getScale(columnIndex), - resultSetMetaData.getInternalColumnType(columnIndex), - resultVersion, - sessionTimeZone, - session); + int columnType = resultSetMetaData.getColumnType(columnIndex); + return converters.getNumberConverter().getBigDecimal(obj, columnType, scale); } @Override public Time getTime(int columnIndex) throws SFException { logger.debug("public Time getTime(int columnIndex)", false); - + Object obj = getObjectInternal(columnIndex); int columnType = resultSetMetaData.getColumnType(columnIndex); - if (Types.TIME == columnType) { - SFTime sfTime = getSFTime(columnIndex); - if (sfTime == null) { - return null; - } - Time ts = - new Time( - sfTime.getFractionalSeconds(ResultUtil.DEFAULT_SCALE_OF_SFTIME_FRACTION_SECONDS)); - if (resultSetSerializable.getUseSessionTimezone()) { - ts = - SnowflakeUtil.getTimeInSessionTimezone( - SnowflakeUtil.getSecondsFromMillis(ts.getTime()), - sfTime.getNanosecondsWithinSecond()); - } - return ts; - } else if (Types.TIMESTAMP == columnType || Types.TIMESTAMP_WITH_TIMEZONE == columnType) { - Timestamp ts = getTimestamp(columnIndex); - if (ts == null) { - return null; - } - if (resultSetSerializable.getUseSessionTimezone()) { - ts = getTimestamp(columnIndex, sessionTimeZone); - TimeZone sessionTimeZone = adjustTimezoneForTimestampTZ(columnIndex); - return new SnowflakeTimeWithTimezone( - ts, sessionTimeZone, resultSetSerializable.getUseSessionTimezone()); - } - return new Time(ts.getTime()); - } else { - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, - columnType, - SnowflakeUtil.TIME_STR, - getObjectInternal(columnIndex)); - } + int columnSubType = resultSetMetaData.getInternalColumnType(columnIndex); + int scale = resultSetMetaData.getScale(columnIndex); + return converters + .getDateTimeConverter() + .getTime(obj, columnType, columnSubType, TimeZone.getDefault(), scale); } @Override public Timestamp getTimestamp(int columnIndex, TimeZone tz) throws SFException { + logger.debug("public Timestamp getTimestamp(int columnIndex)", false); + Object obj = getObjectInternal(columnIndex); int columnType = resultSetMetaData.getColumnType(columnIndex); - if (Types.TIMESTAMP == columnType || Types.TIMESTAMP_WITH_TIMEZONE == columnType) { - if (tz == null) { - tz = TimeZone.getDefault(); - } - SFTimestamp sfTS = getSFTimestamp(columnIndex); - - if (sfTS == null) { - return null; - } - Timestamp res = sfTS.getTimestamp(); - if (res == null) { - return null; - } - int subType = resultSetMetaData.getInternalColumnType(columnIndex); - // If we want to display format with no session offset, we have to use session timezone for - // ltz and tz types but UTC timezone for ntz type. - if (resultSetSerializable.getUseSessionTimezone()) { - if (subType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ - || subType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ) { - TimeZone specificSessionTimezone = adjustTimezoneForTimestampTZ(columnIndex); - res = new SnowflakeTimestampWithTimezone(res, specificSessionTimezone); - } else { - res = new SnowflakeTimestampWithTimezone(res); - } - } - // If timestamp type is NTZ and JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC=true, keep - // timezone in UTC to avoid daylight savings errors - else if (resultSetSerializable.getTreatNTZAsUTC() - && resultSetMetaData.getInternalColumnType(columnIndex) == Types.TIMESTAMP) { - res = new SnowflakeTimestampWithTimezone(res); - } - // If JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC=false, default behavior is to honor - // client timezone for NTZ time. Move NTZ timestamp offset to correspond to - // client's timezone. JDBC_USE_SESSION_TIMEZONE overrides other params. - if (resultSetMetaData.getInternalColumnType(columnIndex) == Types.TIMESTAMP - && ((!resultSetSerializable.getTreatNTZAsUTC() && honorClientTZForTimestampNTZ) - || resultSetSerializable.getUseSessionTimezone())) { - res = sfTS.moveToTimeZone(tz).getTimestamp(); - } - // Adjust time if date happens before year 1582 for difference between - // Julian and Gregorian calendars - return ResultUtil.adjustTimestamp(res); - } else if (Types.DATE == columnType) { - Date d = getDate(columnIndex, tz); - if (d == null) { - return null; - } - return new Timestamp(d.getTime()); - } else if (Types.TIME == columnType) { - Time t = getTime(columnIndex); - if (t == null) { - return null; - } - if (resultSetSerializable.getUseSessionTimezone()) { - SFTime sfTime = getSFTime(columnIndex); - return new SnowflakeTimestampWithTimezone( - sfTime.getFractionalSeconds(ResultUtil.DEFAULT_SCALE_OF_SFTIME_FRACTION_SECONDS), - sfTime.getNanosecondsWithinSecond(), - TimeZone.getTimeZone("UTC")); - } - return new Timestamp(t.getTime()); - } else { - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, - columnType, - SnowflakeUtil.TIMESTAMP_STR, - getObjectInternal(columnIndex)); - } + int columnSubType = resultSetMetaData.getInternalColumnType(columnIndex); + int scale = resultSetMetaData.getScale(columnIndex); + return converters + .getDateTimeConverter() + .getTimestamp(obj, columnType, columnSubType, tz, scale); } @Override public float getFloat(int columnIndex) throws SFException { logger.debug("public float getFloat(int columnIndex)", false); - - // Column index starts from 1, not 0. Object obj = getObjectInternal(columnIndex); - - if (obj == null) { - return 0; - } - int columnType = resultSetMetaData.getColumnType(columnIndex); - try { - if (obj instanceof String) { - if (columnType != Types.TIME - && columnType != Types.TIMESTAMP - && columnType != Types.TIMESTAMP_WITH_TIMEZONE) { - if ("inf".equals(obj)) { - return Float.POSITIVE_INFINITY; - } else if ("-inf".equals(obj)) { - return Float.NEGATIVE_INFINITY; - } else { - return Float.parseFloat((String) obj); - } - } - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, - columnType, - SnowflakeUtil.FLOAT_STR, - getObjectInternal(columnIndex)); - } else { - return ((Number) obj).floatValue(); - } - } catch (NumberFormatException ex) { - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, - columnType, - SnowflakeUtil.FLOAT_STR, - getObjectInternal(columnIndex)); - } + return converters.getNumberConverter().getFloat(obj, columnType); } @Override public double getDouble(int columnIndex) throws SFException { logger.debug("public double getDouble(int columnIndex)", false); - - // Column index starts from 1, not 0. Object obj = getObjectInternal(columnIndex); - - // snow-11974: null for getDouble should return 0 - if (obj == null) { - return 0; - } int columnType = resultSetMetaData.getColumnType(columnIndex); - try { - if (obj instanceof String) { - if (columnType != Types.TIME - && columnType != Types.TIMESTAMP - && columnType != Types.TIMESTAMP_WITH_TIMEZONE) { - if ("inf".equals(obj)) { - return Double.POSITIVE_INFINITY; - } else if ("-inf".equals(obj)) { - return Double.NEGATIVE_INFINITY; - } else { - return Double.parseDouble((String) obj); - } - } - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, - columnType, - SnowflakeUtil.DOUBLE_STR, - getObjectInternal(columnIndex)); - } else { - return ((Number) obj).doubleValue(); - } - } catch (NumberFormatException ex) { - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, - columnType, - SnowflakeUtil.DOUBLE_STR, - getObjectInternal(columnIndex)); - } + return converters.getNumberConverter().getDouble(obj, columnType); } @Override public byte[] getBytes(int columnIndex) throws SFException { logger.debug("public byte[] getBytes(int columnIndex)", false); - - // Column index starts from 1, not 0. Object obj = getObjectInternal(columnIndex); int columnType = resultSetMetaData.getColumnType(columnIndex); - - if (obj == null) { - return null; - } - - try { - // For all types except time/date/timestamp data, convert data into byte array. Different - // methods are needed - // for different types. - switch (columnType) { - case Types.FLOAT: - case Types.DOUBLE: - return ByteBuffer.allocate(Float8Vector.TYPE_WIDTH) - .putDouble(0, getDouble(columnIndex)) - .array(); - case Types.NUMERIC: - case Types.INTEGER: - case Types.SMALLINT: - case Types.TINYINT: - case Types.BIGINT: - return getBigDecimal(columnIndex).toBigInteger().toByteArray(); - case Types.VARCHAR: - case Types.CHAR: - return getString(columnIndex).getBytes(); - case Types.BOOLEAN: - return getBoolean(columnIndex) ? new byte[] {1} : new byte[] {0}; - case Types.TIMESTAMP: - case Types.TIME: - case Types.DATE: - case Types.DECIMAL: - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, - columnType, - SnowflakeUtil.BYTES_STR, - getObjectInternal(columnIndex)); - default: - return SFBinary.fromHex(obj.toString()).getBytes(); - } - } catch (IllegalArgumentException ex) { - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, - columnType, - SnowflakeUtil.BYTES_STR, - getObjectInternal(columnIndex)); - } + int columnSubType = resultSetMetaData.getInternalColumnType(columnIndex); + int scale = resultSetMetaData.getScale(columnIndex); + return converters.getBytesConverter().getBytes(obj, columnType, columnSubType, scale); } public Date getDate(int columnIndex) throws SFException { @@ -673,53 +216,11 @@ public Date getDate(int columnIndex) throws SFException { @Override public Date getDate(int columnIndex, TimeZone tz) throws SFException { logger.debug("public Date getDate(int columnIndex)", false); - - // Column index starts from 1, not 0. Object obj = getObjectInternal(columnIndex); - - if (obj == null) { - return null; - } - int columnType = resultSetMetaData.getColumnType(columnIndex); - - if (Types.TIMESTAMP == columnType || Types.TIMESTAMP_WITH_TIMEZONE == columnType) { - if (tz == null) { - tz = TimeZone.getDefault(); - } - int subType = resultSetMetaData.getInternalColumnType(columnIndex); - if (subType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ - || subType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ) { - TimeZone specificSessionTimeZone = adjustTimezoneForTimestampTZ(columnIndex); - return new SnowflakeDateWithTimezone( - getTimestamp(columnIndex, tz).getTime(), - specificSessionTimeZone, - resultSetSerializable.getUseSessionTimezone()); - } - return new Date(getTimestamp(columnIndex, tz).getTime()); - - } else if (Types.DATE == columnType) { - if (tz == null || !resultSetSerializable.getFormatDateWithTimeZone()) { - return ArrowResultUtil.getDate(Integer.parseInt((String) obj)); - } - return ArrowResultUtil.getDate(Integer.parseInt((String) obj), tz, sessionTimeZone); - } - // for Types.TIME and all other type, throw user error - else { - throw new SFException( - ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.DATE_STR, obj); - } - } - - private SFTime getSFTime(int columnIndex) throws SFException { - Object obj = getObjectInternal(columnIndex); - - if (obj == null) { - return null; - } - + int columnSubType = resultSetMetaData.getInternalColumnType(columnIndex); int scale = resultSetMetaData.getScale(columnIndex); - return ResultUtil.getSFTime(obj.toString(), scale, session); + return converters.getDateTimeConverter().getDate(obj, columnType, columnSubType, tz, scale); } private Timestamp getTimestamp(int columnIndex) throws SFException { diff --git a/src/main/java/net/snowflake/client/core/SFResultSet.java b/src/main/java/net/snowflake/client/core/SFResultSet.java index bfcdb266c..3a32dde6b 100644 --- a/src/main/java/net/snowflake/client/core/SFResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFResultSet.java @@ -12,6 +12,7 @@ import java.util.Arrays; import java.util.Comparator; import net.snowflake.client.core.BasicEvent.QueryState; +import net.snowflake.client.core.json.Converters; import net.snowflake.client.jdbc.*; import net.snowflake.client.jdbc.telemetry.Telemetry; import net.snowflake.client.jdbc.telemetry.TelemetryData; @@ -87,7 +88,11 @@ public SFResultSet( SFBaseStatement statement, boolean sortResult) throws SQLException { - this(resultSetSerializable, statement.getSFBaseSession().getTelemetryClient(), sortResult); + this( + resultSetSerializable, + statement.getSFBaseSession(), + statement.getSFBaseSession().getTelemetryClient(), + sortResult); this.statement = statement; SFBaseSession session = statement.getSFBaseSession(); @@ -118,6 +123,7 @@ public SFResultSet( * * @param resultSetSerializable data returned in query response * @param telemetryClient telemetryClient + * @param sortResult should sorting take place * @throws SQLException */ public SFResultSet( @@ -125,6 +131,41 @@ public SFResultSet( Telemetry telemetryClient, boolean sortResult) throws SQLException { + this(resultSetSerializable, new SFSession(), telemetryClient, sortResult); + } + + /** + * This is a minimum initialization for SFResultSet. Mainly used for testing purpose. However, + * real prod constructor will call this constructor as well + * + * @param resultSetSerializable data returned in query response + * @param session snowflake session + * @param telemetryClient telemetryClient + * @param sortResult should sorting take place + * @throws SQLException + */ + public SFResultSet( + SnowflakeResultSetSerializableV1 resultSetSerializable, + SFBaseSession session, + Telemetry telemetryClient, + boolean sortResult) + throws SQLException { + super( + resultSetSerializable.getTimeZone(), + new Converters( + resultSetSerializable.getTimeZone(), + session, + resultSetSerializable.getResultVersion(), + resultSetSerializable.isHonorClientTZForTimestampNTZ(), + resultSetSerializable.getTreatNTZAsUTC(), + resultSetSerializable.getUseSessionTimezone(), + resultSetSerializable.getFormatDateWithTimeZone(), + resultSetSerializable.getBinaryFormatter(), + resultSetSerializable.getDateFormatter(), + resultSetSerializable.getTimeFormatter(), + resultSetSerializable.getTimestampNTZFormatter(), + resultSetSerializable.getTimestampLTZFormatter(), + resultSetSerializable.getTimestampTZFormatter())); this.resultSetSerializable = resultSetSerializable; this.columnCount = 0; this.sortResult = sortResult; @@ -145,7 +186,6 @@ public SFResultSet( this.timestampTZFormatter = resultSetSerializable.getTimestampTZFormatter(); this.dateFormatter = resultSetSerializable.getDateFormatter(); this.timeFormatter = resultSetSerializable.getTimeFormatter(); - this.sessionTimeZone = resultSetSerializable.getTimeZone(); this.honorClientTZForTimestampNTZ = resultSetSerializable.isHonorClientTZForTimestampNTZ(); this.binaryFormatter = resultSetSerializable.getBinaryFormatter(); this.resultVersion = resultSetSerializable.getResultVersion(); diff --git a/src/main/java/net/snowflake/client/core/json/BooleanConverter.java b/src/main/java/net/snowflake/client/core/json/BooleanConverter.java new file mode 100644 index 000000000..f644c3461 --- /dev/null +++ b/src/main/java/net/snowflake/client/core/json/BooleanConverter.java @@ -0,0 +1,37 @@ +package net.snowflake.client.core.json; + +import java.sql.Types; +import net.snowflake.client.core.SFException; +import net.snowflake.client.jdbc.ErrorCode; +import net.snowflake.client.jdbc.SnowflakeUtil; + +public class BooleanConverter { + public Boolean getBoolean(Object obj, int columnType) throws SFException { + if (obj == null) { + return false; + } + if (obj instanceof Boolean) { + return (Boolean) obj; + } + // if type is an approved type that can be converted to Boolean, do this + if (columnType == Types.BOOLEAN + || columnType == Types.INTEGER + || columnType == Types.SMALLINT + || columnType == Types.TINYINT + || columnType == Types.BIGINT + || columnType == Types.BIT + || columnType == Types.VARCHAR + || columnType == Types.CHAR + || columnType == Types.DECIMAL) { + String type = obj.toString(); + if ("1".equals(type) || Boolean.TRUE.toString().equalsIgnoreCase(type)) { + return true; + } + if ("0".equals(type) || Boolean.FALSE.toString().equalsIgnoreCase(type)) { + return false; + } + } + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.BOOLEAN_STR, obj); + } +} diff --git a/src/main/java/net/snowflake/client/core/json/BytesConverter.java b/src/main/java/net/snowflake/client/core/json/BytesConverter.java new file mode 100644 index 000000000..9e4b01ef5 --- /dev/null +++ b/src/main/java/net/snowflake/client/core/json/BytesConverter.java @@ -0,0 +1,68 @@ +package net.snowflake.client.core.json; + +import java.nio.ByteBuffer; +import java.sql.Types; +import net.snowflake.client.core.SFException; +import net.snowflake.client.jdbc.ErrorCode; +import net.snowflake.client.jdbc.SnowflakeUtil; +import net.snowflake.common.core.SFBinary; +import org.apache.arrow.vector.Float8Vector; + +public class BytesConverter { + private final Converters converters; + + BytesConverter(Converters converters) { + this.converters = converters; + } + + public byte[] getBytes(Object obj, int columnType, int columnSubType, Integer scale) + throws SFException { + if (obj == null) { + return null; + } + + try { + // For all types except time/date/timestamp data, convert data into byte array. Different + // methods are needed + // for different types. + switch (columnType) { + case Types.FLOAT: + case Types.DOUBLE: + return ByteBuffer.allocate(Float8Vector.TYPE_WIDTH) + .putDouble(0, converters.getNumberConverter().getDouble(obj, columnType)) + .array(); + case Types.NUMERIC: + case Types.INTEGER: + case Types.SMALLINT: + case Types.TINYINT: + case Types.BIGINT: + return converters + .getNumberConverter() + .getBigDecimal(obj, columnType, scale) + .toBigInteger() + .toByteArray(); + case Types.VARCHAR: + case Types.CHAR: + return converters + .getStringConverter() + .getString(obj, columnType, columnSubType, scale) + .getBytes(); + case Types.BOOLEAN: + return converters.getBooleanConverter().getBoolean(obj, columnType) + ? new byte[] {1} + : new byte[] {0}; + case Types.TIMESTAMP: + case Types.TIME: + case Types.DATE: + case Types.DECIMAL: + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.BYTES_STR, obj); + default: + return SFBinary.fromHex(obj.toString()).getBytes(); + } + } catch (IllegalArgumentException ex) { + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.BYTES_STR, obj); + } + } +} diff --git a/src/main/java/net/snowflake/client/core/json/Converters.java b/src/main/java/net/snowflake/client/core/json/Converters.java new file mode 100644 index 000000000..fa3baadb6 --- /dev/null +++ b/src/main/java/net/snowflake/client/core/json/Converters.java @@ -0,0 +1,74 @@ +package net.snowflake.client.core.json; + +import java.util.TimeZone; +import net.snowflake.client.core.SFBaseSession; +import net.snowflake.common.core.SFBinaryFormat; +import net.snowflake.common.core.SnowflakeDateTimeFormat; + +public class Converters { + private final BooleanConverter booleanConverter; + private final NumberConverter numberConverter; + private final DateTimeConverter dateTimeConverter; + private final BytesConverter bytesConverter; + private final StringConverter stringConverter; + + public Converters( + TimeZone sessionTimeZone, + SFBaseSession session, + long resultVersion, + boolean honorClientTZForTimestampNTZ, + boolean treatNTZAsUTC, + boolean useSessionTimezone, + boolean formatDateWithTimeZone, + SFBinaryFormat binaryFormatter, + SnowflakeDateTimeFormat dateFormatter, + SnowflakeDateTimeFormat timeFormatter, + SnowflakeDateTimeFormat timestampNTZFormatter, + SnowflakeDateTimeFormat timestampLTZFormatter, + SnowflakeDateTimeFormat timestampTZFormatter) { + booleanConverter = new BooleanConverter(); + numberConverter = new NumberConverter(); + dateTimeConverter = + new DateTimeConverter( + sessionTimeZone, + session, + resultVersion, + honorClientTZForTimestampNTZ, + treatNTZAsUTC, + useSessionTimezone, + formatDateWithTimeZone); + bytesConverter = new BytesConverter(this); + stringConverter = + new StringConverter( + sessionTimeZone, + binaryFormatter, + dateFormatter, + timeFormatter, + timestampNTZFormatter, + timestampLTZFormatter, + timestampTZFormatter, + resultVersion, + session, + this); + } + + public BooleanConverter getBooleanConverter() { + return booleanConverter; + } + + public NumberConverter getNumberConverter() { + return numberConverter; + } + + public DateTimeConverter getDateTimeConverter() { + return dateTimeConverter; + } + + public BytesConverter getBytesConverter() { + return bytesConverter; + } + + public StringConverter getStringConverter() { + return stringConverter; + } +} diff --git a/src/main/java/net/snowflake/client/core/json/DateTimeConverter.java b/src/main/java/net/snowflake/client/core/json/DateTimeConverter.java new file mode 100644 index 000000000..2622e259a --- /dev/null +++ b/src/main/java/net/snowflake/client/core/json/DateTimeConverter.java @@ -0,0 +1,195 @@ +package net.snowflake.client.core.json; + +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.TimeZone; +import net.snowflake.client.core.ResultUtil; +import net.snowflake.client.core.SFBaseSession; +import net.snowflake.client.core.SFException; +import net.snowflake.client.core.arrow.ArrowResultUtil; +import net.snowflake.client.jdbc.ErrorCode; +import net.snowflake.client.jdbc.SnowflakeDateWithTimezone; +import net.snowflake.client.jdbc.SnowflakeTimeWithTimezone; +import net.snowflake.client.jdbc.SnowflakeTimestampWithTimezone; +import net.snowflake.client.jdbc.SnowflakeUtil; +import net.snowflake.common.core.SFTime; +import net.snowflake.common.core.SFTimestamp; + +public class DateTimeConverter { + private final TimeZone sessionTimeZone; + private final long resultVersion; + private final boolean honorClientTZForTimestampNTZ; + private final boolean treatNTZAsUTC; + private final boolean useSessionTimezone; + private final boolean formatDateWithTimeZone; + private final SFBaseSession session; + + public DateTimeConverter( + TimeZone sessionTimeZone, + SFBaseSession session, + long resultVersion, + boolean honorClientTZForTimestampNTZ, + boolean treatNTZAsUTC, + boolean useSessionTimezone, + boolean formatDateWithTimeZone) { + this.sessionTimeZone = sessionTimeZone; + this.session = session; + this.resultVersion = resultVersion; + this.honorClientTZForTimestampNTZ = honorClientTZForTimestampNTZ; + this.treatNTZAsUTC = treatNTZAsUTC; + this.useSessionTimezone = useSessionTimezone; + this.formatDateWithTimeZone = formatDateWithTimeZone; + } + + public Timestamp getTimestamp( + Object obj, int columnType, int columnSubType, TimeZone tz, int scale) throws SFException { + if (obj == null) { + return null; + } + if (Types.TIMESTAMP == columnType || Types.TIMESTAMP_WITH_TIMEZONE == columnType) { + if (tz == null) { + tz = TimeZone.getDefault(); + } + SFTimestamp sfTS = + ResultUtil.getSFTimestamp( + obj.toString(), scale, columnSubType, resultVersion, sessionTimeZone, session); + + Timestamp res = sfTS.getTimestamp(); + if (res == null) { + return null; + } + // If we want to display format with no session offset, we have to use session timezone for + // ltz and tz types but UTC timezone for ntz type. + if (useSessionTimezone) { + if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ + || columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ) { + TimeZone specificSessionTimezone = adjustTimezoneForTimestampTZ(obj, columnSubType); + res = new SnowflakeTimestampWithTimezone(res, specificSessionTimezone); + } else { + res = new SnowflakeTimestampWithTimezone(res); + } + } + // If timestamp type is NTZ and JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC=true, keep + // timezone in UTC to avoid daylight savings errors + else if (treatNTZAsUTC && columnSubType == Types.TIMESTAMP) { + res = new SnowflakeTimestampWithTimezone(res); + } + // If JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC=false, default behavior is to honor + // client timezone for NTZ time. Move NTZ timestamp offset to correspond to + // client's timezone. JDBC_USE_SESSION_TIMEZONE overrides other params. + if (columnSubType == Types.TIMESTAMP + && ((!treatNTZAsUTC && honorClientTZForTimestampNTZ) || useSessionTimezone)) { + res = sfTS.moveToTimeZone(tz).getTimestamp(); + } + // Adjust time if date happens before year 1582 for difference between + // Julian and Gregorian calendars + return ResultUtil.adjustTimestamp(res); + } else if (Types.DATE == columnType) { + Date d = getDate(obj, columnType, columnSubType, tz, scale); + if (d == null) { + return null; + } + return new Timestamp(d.getTime()); + } else if (Types.TIME == columnType) { + Time t = getTime(obj, columnType, columnSubType, tz, scale); + if (t == null) { + return null; + } + if (useSessionTimezone) { + SFTime sfTime = ResultUtil.getSFTime(obj.toString(), scale, session); + return new SnowflakeTimestampWithTimezone( + sfTime.getFractionalSeconds(ResultUtil.DEFAULT_SCALE_OF_SFTIME_FRACTION_SECONDS), + sfTime.getNanosecondsWithinSecond(), + TimeZone.getTimeZone("UTC")); + } + return new Timestamp(t.getTime()); + } else { + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.TIMESTAMP_STR, obj); + } + } + + public Time getTime(Object obj, int columnType, int columnSubType, TimeZone tz, int scale) + throws SFException { + if (obj == null) { + return null; + } + if (Types.TIME == columnType) { + SFTime sfTime = ResultUtil.getSFTime(obj.toString(), scale, session); + Time ts = + new Time( + sfTime.getFractionalSeconds(ResultUtil.DEFAULT_SCALE_OF_SFTIME_FRACTION_SECONDS)); + if (useSessionTimezone) { + ts = + SnowflakeUtil.getTimeInSessionTimezone( + SnowflakeUtil.getSecondsFromMillis(ts.getTime()), + sfTime.getNanosecondsWithinSecond()); + } + return ts; + } else if (Types.TIMESTAMP == columnType || Types.TIMESTAMP_WITH_TIMEZONE == columnType) { + Timestamp ts = getTimestamp(obj, columnType, columnSubType, tz, scale); + if (ts == null) { + return null; + } + if (useSessionTimezone) { + ts = getTimestamp(obj, columnType, columnSubType, sessionTimeZone, scale); + TimeZone sessionTimeZone = adjustTimezoneForTimestampTZ(obj, columnSubType); + return new SnowflakeTimeWithTimezone(ts, sessionTimeZone, useSessionTimezone); + } + return new Time(ts.getTime()); + } else { + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.TIME_STR, obj); + } + } + + public Date getDate(Object obj, int columnType, int columnSubType, TimeZone tz, int scale) + throws SFException { + if (obj == null) { + return null; + } + + if (Types.TIMESTAMP == columnType || Types.TIMESTAMP_WITH_TIMEZONE == columnType) { + if (tz == null) { + tz = TimeZone.getDefault(); + } + if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ + || columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ) { + TimeZone specificSessionTimeZone = adjustTimezoneForTimestampTZ(obj, columnSubType); + return new SnowflakeDateWithTimezone( + getTimestamp(obj, columnType, columnSubType, tz, scale).getTime(), + specificSessionTimeZone, + useSessionTimezone); + } + return new Date(getTimestamp(obj, columnType, columnSubType, tz, scale).getTime()); + + } else if (Types.DATE == columnType) { + if (tz == null || !formatDateWithTimeZone) { + return ArrowResultUtil.getDate(Integer.parseInt((String) obj)); + } + return ArrowResultUtil.getDate(Integer.parseInt((String) obj), tz, sessionTimeZone); + } + // for Types.TIME and all other type, throw user error + else { + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.DATE_STR, obj); + } + } + + private TimeZone adjustTimezoneForTimestampTZ(Object obj, int columnSubType) { + // If the timestamp is of type timestamp_tz, use the associated offset timezone instead of the + // session timezone for formatting + if (obj != null + && columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ + && resultVersion > 0) { + String timestampStr = obj.toString(); + int indexForSeparator = timestampStr.indexOf(' '); + String timezoneIndexStr = timestampStr.substring(indexForSeparator + 1); + return SFTimestamp.convertTimezoneIndexToTimeZone(Integer.parseInt(timezoneIndexStr)); + } + // By default, return session timezone + return sessionTimeZone; + } +} diff --git a/src/main/java/net/snowflake/client/core/json/NumberConverter.java b/src/main/java/net/snowflake/client/core/json/NumberConverter.java new file mode 100644 index 000000000..132003359 --- /dev/null +++ b/src/main/java/net/snowflake/client/core/json/NumberConverter.java @@ -0,0 +1,192 @@ +package net.snowflake.client.core.json; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.Types; +import net.snowflake.client.core.SFException; +import net.snowflake.client.jdbc.ErrorCode; +import net.snowflake.client.jdbc.SnowflakeUtil; + +public class NumberConverter { + // Precision of maximum long value in Java (2^63-1). Precision is 19 + private static final int LONG_PRECISION = 19; + + private static final BigDecimal MAX_LONG_VAL = new BigDecimal(Long.MAX_VALUE); + private static final BigDecimal MIN_LONG_VAL = new BigDecimal(Long.MIN_VALUE); + + public byte getByte(Object obj) { + if (obj == null) { + return 0; + } + + if (obj instanceof String) { + return Byte.parseByte((String) obj); + } else { + return ((Number) obj).byteValue(); + } + } + + public short getShort(Object obj, int columnType) throws SFException { + if (obj == null) { + return 0; + } + try { + if (obj instanceof String) { + String objString = (String) obj; + if (objString.contains(".") && (columnType == Types.FLOAT || columnType == Types.DOUBLE)) { + objString = objString.substring(0, objString.indexOf(".")); + } + return Short.parseShort(objString); + } else { + return ((Number) obj).shortValue(); + } + } catch (NumberFormatException ex) { + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.SHORT_STR, obj); + } + } + + public int getInt(Object obj, int columnType) throws SFException { + if (obj == null) { + return 0; + } + try { + if (obj instanceof String) { + String objString = (String) obj; + if (objString.contains(".") && (columnType == Types.FLOAT || columnType == Types.DOUBLE)) { + objString = objString.substring(0, objString.indexOf(".")); + } + return Integer.parseInt(objString); + } else { + return ((Number) obj).intValue(); + } + } catch (NumberFormatException ex) { + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.INT_STR, obj); + } + } + + public long getLong(Object obj, int columnType) throws SFException { + if (obj == null) { + return 0; + } + try { + if (obj instanceof String) { + String objString = (String) obj; + if (objString.contains(".") && (columnType == Types.FLOAT || columnType == Types.DOUBLE)) { + objString = objString.substring(0, objString.indexOf(".")); + } + return Long.parseLong(objString); + } else { + return ((Number) obj).longValue(); + } + } catch (NumberFormatException nfe) { + + if (Types.INTEGER == columnType || Types.SMALLINT == columnType) { + throw new SFException( + ErrorCode.INTERNAL_ERROR, SnowflakeUtil.LONG_STR + ": " + obj.toString()); + } else { + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.LONG_STR, obj); + } + } + } + + public BigDecimal getBigDecimal(Object obj, int columnType) throws SFException { + if (obj == null) { + return null; + } + try { + if (columnType != Types.TIME + && columnType != Types.TIMESTAMP + && columnType != Types.TIMESTAMP_WITH_TIMEZONE) { + return new BigDecimal(obj.toString()); + } + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.BIG_DECIMAL_STR, obj); + + } catch (NumberFormatException ex) { + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.BIG_DECIMAL_STR, obj); + } + } + + public BigDecimal getBigDecimal(Object obj, int columnType, Integer scale) throws SFException { + if (obj == null) { + return null; + } + BigDecimal value = new BigDecimal(obj.toString()); + value = value.setScale(scale, RoundingMode.HALF_UP); + return value; + } + + public float getFloat(Object obj, int columnType) throws SFException { + if (obj == null) { + return 0; + } + + try { + if (obj instanceof String) { + if (columnType != Types.TIME + && columnType != Types.TIMESTAMP + && columnType != Types.TIMESTAMP_WITH_TIMEZONE) { + if ("inf".equals(obj)) { + return Float.POSITIVE_INFINITY; + } else if ("-inf".equals(obj)) { + return Float.NEGATIVE_INFINITY; + } else { + return Float.parseFloat((String) obj); + } + } + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.FLOAT_STR, obj); + } else { + return ((Number) obj).floatValue(); + } + } catch (NumberFormatException ex) { + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.FLOAT_STR, obj); + } + } + + public double getDouble(Object obj, int columnType) throws SFException { + if (obj == null) { + return 0; + } + try { + if (obj instanceof String) { + if (columnType != Types.TIME + && columnType != Types.TIMESTAMP + && columnType != Types.TIMESTAMP_WITH_TIMEZONE) { + if ("inf".equals(obj)) { + return Double.POSITIVE_INFINITY; + } else if ("-inf".equals(obj)) { + return Double.NEGATIVE_INFINITY; + } else { + return Double.parseDouble((String) obj); + } + } + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.DOUBLE_STR, obj); + } else { + return ((Number) obj).doubleValue(); + } + } catch (NumberFormatException ex) { + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.DOUBLE_STR, obj); + } + } + + public Object getBigInt(Object obj, int columnType) throws SFException { + // If precision is < precision of max long precision, we can automatically convert to long. + // Otherwise, do a check to ensure it doesn't overflow max long value. + String numberAsString = obj.toString(); + if (numberAsString.length() >= LONG_PRECISION) { + BigDecimal bigNum = getBigDecimal(obj, columnType); + if (bigNum.compareTo(MAX_LONG_VAL) == 1 || bigNum.compareTo(MIN_LONG_VAL) == -1) { + return bigNum; + } + } + return getLong(obj, columnType); + } +} diff --git a/src/main/java/net/snowflake/client/core/json/StringConverter.java b/src/main/java/net/snowflake/client/core/json/StringConverter.java new file mode 100644 index 000000000..4b5684910 --- /dev/null +++ b/src/main/java/net/snowflake/client/core/json/StringConverter.java @@ -0,0 +1,155 @@ +package net.snowflake.client.core.json; + +import java.sql.Date; +import java.sql.Types; +import java.util.TimeZone; +import net.snowflake.client.core.ResultUtil; +import net.snowflake.client.core.SFBaseSession; +import net.snowflake.client.core.SFException; +import net.snowflake.client.jdbc.ErrorCode; +import net.snowflake.client.jdbc.SnowflakeUtil; +import net.snowflake.client.log.ArgSupplier; +import net.snowflake.client.log.SFLogger; +import net.snowflake.client.log.SFLoggerFactory; +import net.snowflake.common.core.SFBinary; +import net.snowflake.common.core.SFBinaryFormat; +import net.snowflake.common.core.SFTime; +import net.snowflake.common.core.SFTimestamp; +import net.snowflake.common.core.SnowflakeDateTimeFormat; + +public class StringConverter { + private static final SFLogger logger = SFLoggerFactory.getLogger(StringConverter.class); + private final TimeZone sessionTimeZone; + private final SFBinaryFormat binaryFormatter; + private final SnowflakeDateTimeFormat dateFormatter; + private final SnowflakeDateTimeFormat timeFormatter; + private final SnowflakeDateTimeFormat timestampNTZFormatter; + private final SnowflakeDateTimeFormat timestampLTZFormatter; + private final SnowflakeDateTimeFormat timestampTZFormatter; + private final long resultVersion; + private final SFBaseSession session; + private final Converters converters; + + public StringConverter( + TimeZone sessionTimeZone, + SFBinaryFormat binaryFormatter, + SnowflakeDateTimeFormat dateFormatter, + SnowflakeDateTimeFormat timeFormatter, + SnowflakeDateTimeFormat timestampNTZFormatter, + SnowflakeDateTimeFormat timestampLTZFormatter, + SnowflakeDateTimeFormat timestampTZFormatter, + long resultVersion, + SFBaseSession session, + Converters converters) { + this.sessionTimeZone = sessionTimeZone; + this.binaryFormatter = binaryFormatter; + this.dateFormatter = dateFormatter; + this.timeFormatter = timeFormatter; + this.timestampNTZFormatter = timestampNTZFormatter; + this.timestampLTZFormatter = timestampLTZFormatter; + this.timestampTZFormatter = timestampTZFormatter; + this.resultVersion = resultVersion; + this.session = session; + this.converters = converters; + } + + public String getString(Object obj, int columnType, int columnSubType, int scale) + throws SFException { + if (obj == null) { + return null; + } + + switch (columnType) { + case Types.BOOLEAN: + return ResultUtil.getBooleanAsString(ResultUtil.getBoolean(obj.toString())); + + case Types.TIMESTAMP: + case SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ: + case SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ: + return timestampToString(obj, columnType, columnSubType, scale); + case Types.DATE: + return dateToString(obj, columnType, columnSubType, scale); + case Types.TIME: + return timeToString(obj, scale); + case Types.BINARY: + return binaryToString(obj, columnType, columnSubType, scale); + default: + break; + } + return obj.toString(); + } + + private String timestampToString(Object obj, int columnType, int columnSubType, int scale) + throws SFException { + SFTimestamp sfTS = + ResultUtil.getSFTimestamp( + obj.toString(), scale, columnSubType, resultVersion, sessionTimeZone, session); + + String timestampStr = + ResultUtil.getSFTimestampAsString( + sfTS, + columnType, + scale, + timestampNTZFormatter, + timestampLTZFormatter, + timestampTZFormatter, + session); + + logger.debug( + "Converting timestamp to string from: {} to: {}", + (ArgSupplier) obj::toString, + timestampStr); + + return timestampStr; + } + + private String dateToString(Object obj, int columnType, int columnSubType, int scale) + throws SFException { + Date date = + converters + .getDateTimeConverter() + .getDate(obj, columnType, columnSubType, TimeZone.getDefault(), scale); + + if (dateFormatter == null) { + throw new SFException(ErrorCode.INTERNAL_ERROR, "missing date formatter"); + } + + String dateStr = ResultUtil.getDateAsString(date, dateFormatter); + + logger.debug("Converting date to string from: {} to: {}", (ArgSupplier) obj::toString, dateStr); + + return dateStr; + } + + private String timeToString(Object obj, int scale) throws SFException { + SFTime sfTime = ResultUtil.getSFTime(obj.toString(), scale, session); + + if (timeFormatter == null) { + throw new SFException(ErrorCode.INTERNAL_ERROR, "missing time formatter"); + } + + String timeStr = ResultUtil.getSFTimeAsString(sfTime, scale, timeFormatter); + + logger.debug("Converting time to string from: {} to: {}", (ArgSupplier) obj::toString, timeStr); + + return timeStr; + } + + private String binaryToString(Object obj, int columnType, int columnSubType, int scale) + throws SFException { + if (binaryFormatter == null) { + throw new SFException(ErrorCode.INTERNAL_ERROR, "missing binary formatter"); + } + + if (binaryFormatter == SFBinaryFormat.HEX) { + // Shortcut: the values are already passed with hex encoding, so just + // return the string unchanged rather than constructing an SFBinary. + return obj.toString(); + } + + SFBinary sfb = + new SFBinary( + converters.getBytesConverter().getBytes(obj, columnType, columnSubType, scale)); + return binaryFormatter.format(sfb); + } +} diff --git a/src/main/java/net/snowflake/client/jdbc/RestRequest.java b/src/main/java/net/snowflake/client/jdbc/RestRequest.java index b31227223..b6f1f7780 100644 --- a/src/main/java/net/snowflake/client/jdbc/RestRequest.java +++ b/src/main/java/net/snowflake/client/jdbc/RestRequest.java @@ -7,12 +7,11 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.concurrent.atomic.AtomicBoolean; -import javax.net.ssl.*; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLKeyException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLProtocolException; import net.snowflake.client.core.*; -import net.snowflake.client.core.Event; -import net.snowflake.client.core.EventUtil; -import net.snowflake.client.core.HttpUtil; -import net.snowflake.client.core.SFOCSPException; import net.snowflake.client.jdbc.telemetryOOB.TelemetryService; import net.snowflake.client.log.ArgSupplier; import net.snowflake.client.log.SFLogger; diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetSerializableV1.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetSerializableV1.java index 764acdb9b..d5cf0c1c2 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetSerializableV1.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetSerializableV1.java @@ -1087,7 +1087,9 @@ private ResultSet getResultSetInternal(Properties info) throws SQLException { } case JSON: { - sfBaseResultSet = new SFResultSet(this, telemetryClient, sortResult); + sfBaseResultSet = + new SFResultSet( + this, getSession().orElse(new SFSession()), telemetryClient, sortResult); break; } default: diff --git a/src/test/java/net/snowflake/client/core/json/BooleanConverterTest.java b/src/test/java/net/snowflake/client/core/json/BooleanConverterTest.java new file mode 100644 index 000000000..2162d651a --- /dev/null +++ b/src/test/java/net/snowflake/client/core/json/BooleanConverterTest.java @@ -0,0 +1,51 @@ +package net.snowflake.client.core.json; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +import java.sql.Types; +import net.snowflake.client.core.SFException; +import org.junit.Test; + +public class BooleanConverterTest { + private final BooleanConverter booleanConverter = new BooleanConverter(); + + @Test + public void testConvertBoolean() throws SFException { + assertThat(booleanConverter.getBoolean(true, Types.BOOLEAN), equalTo(true)); + assertThat(booleanConverter.getBoolean(false, Types.BOOLEAN), equalTo(false)); + } + + @Test + public void testConvertNumeric() throws SFException { + assertThat(booleanConverter.getBoolean(1, Types.INTEGER), equalTo(true)); + assertThat(booleanConverter.getBoolean(1, Types.SMALLINT), equalTo(true)); + assertThat(booleanConverter.getBoolean(1, Types.TINYINT), equalTo(true)); + assertThat(booleanConverter.getBoolean(1, Types.BIGINT), equalTo(true)); + assertThat(booleanConverter.getBoolean(1, Types.BIT), equalTo(true)); + assertThat(booleanConverter.getBoolean(1, Types.DECIMAL), equalTo(true)); + assertThat(booleanConverter.getBoolean(0, Types.INTEGER), equalTo(false)); + assertThat(booleanConverter.getBoolean(0, Types.SMALLINT), equalTo(false)); + assertThat(booleanConverter.getBoolean(0, Types.TINYINT), equalTo(false)); + assertThat(booleanConverter.getBoolean(0, Types.BIGINT), equalTo(false)); + assertThat(booleanConverter.getBoolean(0, Types.BIT), equalTo(false)); + assertThat(booleanConverter.getBoolean(0, Types.DECIMAL), equalTo(false)); + } + + @Test + public void testConvertString() throws SFException { + assertThat(booleanConverter.getBoolean("1", Types.VARCHAR), equalTo(true)); + assertThat(booleanConverter.getBoolean("1", Types.CHAR), equalTo(true)); + assertThat(booleanConverter.getBoolean("true", Types.VARCHAR), equalTo(true)); + assertThat(booleanConverter.getBoolean("TRUE", Types.CHAR), equalTo(true)); + assertThat(booleanConverter.getBoolean("0", Types.VARCHAR), equalTo(false)); + assertThat(booleanConverter.getBoolean("0", Types.CHAR), equalTo(false)); + assertThat(booleanConverter.getBoolean("false", Types.VARCHAR), equalTo(false)); + assertThat(booleanConverter.getBoolean("FALSE", Types.CHAR), equalTo(false)); + } + + @Test(expected = SFException.class) + public void testConvertOtherType() throws SFException { + booleanConverter.getBoolean("1", Types.BINARY); + } +} diff --git a/src/test/java/net/snowflake/client/core/json/BytesConverterTest.java b/src/test/java/net/snowflake/client/core/json/BytesConverterTest.java new file mode 100644 index 000000000..47e898486 --- /dev/null +++ b/src/test/java/net/snowflake/client/core/json/BytesConverterTest.java @@ -0,0 +1,51 @@ +package net.snowflake.client.core.json; + +import static org.junit.Assert.assertArrayEquals; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.sql.Types; +import net.snowflake.client.core.SFException; +import net.snowflake.client.core.SFSession; +import org.apache.arrow.vector.Float8Vector; +import org.junit.Test; + +public class BytesConverterTest { + private final Converters converters = + new Converters( + null, new SFSession(), 0, false, false, false, false, null, null, null, null, null, null); + private final BytesConverter bytesConverter = new BytesConverter(converters); + + @Test + public void testConvertFloatingPointToBytes() throws SFException { + byte[] expected = ByteBuffer.allocate(Float8Vector.TYPE_WIDTH).putDouble(0, 12.5).array(); + assertArrayEquals(expected, bytesConverter.getBytes(12.5f, Types.FLOAT, Types.FLOAT, 0)); + assertArrayEquals(expected, bytesConverter.getBytes(12.5f, Types.DOUBLE, Types.DOUBLE, 0)); + assertArrayEquals(expected, bytesConverter.getBytes(12.5, Types.FLOAT, Types.DOUBLE, 0)); + assertArrayEquals(expected, bytesConverter.getBytes(12.5, Types.DOUBLE, Types.DOUBLE, 0)); + } + + @Test + public void testConvertIntegerNumberToBytes() throws SFException { + byte[] expected = new BigInteger("12").toByteArray(); + assertArrayEquals(expected, bytesConverter.getBytes(12, Types.NUMERIC, Types.NUMERIC, 0)); + assertArrayEquals(expected, bytesConverter.getBytes(12, Types.INTEGER, Types.INTEGER, 0)); + assertArrayEquals(expected, bytesConverter.getBytes(12, Types.SMALLINT, Types.SMALLINT, 0)); + assertArrayEquals(expected, bytesConverter.getBytes(12, Types.TINYINT, Types.TINYINT, 0)); + assertArrayEquals(expected, bytesConverter.getBytes(12, Types.BIGINT, Types.BIGINT, 0)); + } + + @Test + public void testString() throws SFException { + assertArrayEquals( + "abc".getBytes(), bytesConverter.getBytes("abc", Types.VARCHAR, Types.VARCHAR, 0)); + } + + @Test + public void testConvertBooleanToBytes() throws SFException { + assertArrayEquals( + new byte[] {1}, bytesConverter.getBytes(true, Types.BOOLEAN, Types.BOOLEAN, 0)); + assertArrayEquals( + new byte[] {0}, bytesConverter.getBytes(false, Types.BOOLEAN, Types.BOOLEAN, 0)); + } +} diff --git a/src/test/java/net/snowflake/client/core/json/DateTimeConverterTest.java b/src/test/java/net/snowflake/client/core/json/DateTimeConverterTest.java new file mode 100644 index 000000000..985264f3e --- /dev/null +++ b/src/test/java/net/snowflake/client/core/json/DateTimeConverterTest.java @@ -0,0 +1,147 @@ +package net.snowflake.client.core.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.util.TimeZone; +import net.snowflake.client.core.SFException; +import net.snowflake.client.core.SFSession; +import net.snowflake.client.jdbc.SnowflakeUtil; +import org.junit.Test; + +public class DateTimeConverterTest { + private final TimeZone honoluluTimeZone = + TimeZone.getTimeZone(ZoneId.of("Pacific/Honolulu")); // session time zone + private final TimeZone nuukTimeZone = TimeZone.getTimeZone(ZoneId.of("America/Nuuk")); + private final DateTimeConverter dateTimeConverter = + new DateTimeConverter(honoluluTimeZone, new SFSession(), 1, true, false, false, false); + private final DateTimeConverter dateTimeConverterWithUseSessionTimeZone = + new DateTimeConverter(honoluluTimeZone, new SFSession(), 1, true, false, true, false); + private final DateTimeConverter dateTimeConverterWithTreatNTZAsUTC = + new DateTimeConverter(honoluluTimeZone, new SFSession(), 1, true, true, false, false); + + @Test + public void testGetVariousTypesWhenNullObjectGiven() throws SFException { + assertNull(dateTimeConverter.getTimestamp(null, Types.TIMESTAMP, Types.TIMESTAMP, null, 0)); + assertNull(dateTimeConverter.getTime(null, Types.TIMESTAMP, Types.TIMESTAMP, null, 0)); + assertNull(dateTimeConverter.getDate(null, Types.TIMESTAMP, Types.TIMESTAMP, null, 0)); + } + + @Test + public void testGetTimestampWithDefaultTimeZone() throws SFException { + assertEquals( + Timestamp.valueOf(LocalDateTime.of(2023, 8, 9, 8, 2, 3)), + dateTimeConverter.getTimestamp("1691568123", Types.TIMESTAMP, Types.TIMESTAMP, null, 0)); + assertEquals( + Timestamp.valueOf(LocalDateTime.of(2023, 8, 9, 8, 2, 3, 456789000)), + dateTimeConverter.getTimestamp( + "1691568123.456789", Types.TIMESTAMP, Types.TIMESTAMP, null, 0)); + assertEquals( + Timestamp.valueOf(LocalDateTime.of(2023, 8, 9, 8, 2, 3, 456789123)), + dateTimeConverter.getTimestamp( + "1691568123.456789123", Types.TIMESTAMP, Types.TIMESTAMP, null, 0)); + } + + @Test + public void testGetTimestampWithSpecificTimeZone() throws SFException { + assertEquals( + Timestamp.valueOf(LocalDateTime.of(2023, 8, 9, 8, 2, 3)).toString(), + dateTimeConverterWithTreatNTZAsUTC + .getTimestamp("1691568123", Types.TIMESTAMP, Types.TIMESTAMP, nuukTimeZone, 0) + .toString()); + assertEquals( + Timestamp.valueOf(LocalDateTime.of(2023, 8, 9, 8, 2, 3, 456789000)).toString(), + dateTimeConverterWithTreatNTZAsUTC + .getTimestamp("1691568123.456789", Types.TIMESTAMP, Types.TIMESTAMP, nuukTimeZone, 0) + .toString()); + assertEquals( + Timestamp.valueOf(LocalDateTime.of(2023, 8, 9, 8, 2, 3, 456789123)).toString(), + dateTimeConverterWithTreatNTZAsUTC + .getTimestamp("1691568123.456789123", Types.TIMESTAMP, Types.TIMESTAMP, nuukTimeZone, 0) + .toString()); + } + + // TODO replace equality when SNOW-991418 is fixed + @Test + public void testGetTimeWithDefaultTimeZone() throws SFException { + assertEquals( + Time.valueOf(LocalTime.of(8, 2, 3)).toString(), + dateTimeConverter + .getTime("1691568123", Types.TIMESTAMP, Types.TIMESTAMP, null, 0) + .toString()); + assertEquals( + Time.valueOf(LocalTime.of(8, 2, 3)).toString(), + dateTimeConverter + .getTime("1691568123.456789", Types.TIMESTAMP, Types.TIMESTAMP, null, 0) + .toString()); + assertEquals( + Time.valueOf(LocalTime.of(8, 2, 3)).toString(), + dateTimeConverter + .getTime("1691568123.456789123", Types.TIMESTAMP, Types.TIMESTAMP, null, 0) + .toString()); + } + + @Test + public void testGetTimeWithSessionTimeZone() throws SFException { + Time expected = Time.valueOf(LocalTime.of(22, 2, 3)); + Time actual = + dateTimeConverterWithUseSessionTimeZone.getTime( + "1691568123", Types.TIMESTAMP, SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ, null, 0); + assertEquals(expected.toString(), actual.toString()); + } + + @Test + public void testGetDateWithDefaultTimeZone() throws SFException { + assertEquals( + Date.valueOf(LocalDate.of(2023, 8, 9)).toString(), + dateTimeConverter + .getDate("1691568123", Types.TIMESTAMP, Types.TIMESTAMP, null, 0) + .toString()); + assertEquals( + Date.valueOf(LocalDate.of(2023, 8, 9)).toString(), + dateTimeConverter + .getDate("1691568123.456789", Types.TIMESTAMP, Types.TIMESTAMP, null, 0) + .toString()); + assertEquals( + Date.valueOf(LocalDate.of(2023, 8, 9)).toString(), + dateTimeConverter + .getDate("1691568123.456789123", Types.TIMESTAMP, Types.TIMESTAMP, null, 0) + .toString()); + } + + @Test + public void testGetDateWithSpecificTimeZone() throws SFException { + assertEquals( + Date.valueOf(LocalDate.of(2023, 8, 9)).toString(), + dateTimeConverter + .getDate("1691568123", Types.TIMESTAMP, Types.TIMESTAMP, nuukTimeZone, 0) + .toString()); + assertEquals( + Date.valueOf(LocalDate.of(2023, 8, 9)).toString(), + dateTimeConverter + .getDate("1691568123.456789", Types.TIMESTAMP, Types.TIMESTAMP, nuukTimeZone, 0) + .toString()); + assertEquals( + Date.valueOf(LocalDate.of(2023, 8, 9)).toString(), + dateTimeConverter + .getDate("1691568123.456789123", Types.TIMESTAMP, Types.TIMESTAMP, nuukTimeZone, 0) + .toString()); + } + + @Test + public void testGetDateWithSessionTimeZone() throws SFException { + Date expected = Date.valueOf(LocalDate.of(2023, 8, 8)); + Date actual = + dateTimeConverterWithUseSessionTimeZone.getDate( + "1691568123", Types.TIMESTAMP, SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ, null, 0); + assertEquals(expected.toString(), actual.toString()); + } +} diff --git a/src/test/java/net/snowflake/client/core/json/NumberConverterTest.java b/src/test/java/net/snowflake/client/core/json/NumberConverterTest.java new file mode 100644 index 000000000..c37573b72 --- /dev/null +++ b/src/test/java/net/snowflake/client/core/json/NumberConverterTest.java @@ -0,0 +1,163 @@ +package net.snowflake.client.core.json; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +import java.math.BigDecimal; +import java.sql.Types; +import net.snowflake.client.core.SFException; +import org.junit.Test; + +public class NumberConverterTest { + private final NumberConverter numberConverter = new NumberConverter(); + + @Test + public void testByteFromNumber() { + assertThat(numberConverter.getByte(1), equalTo((byte) 1)); + assertThat(numberConverter.getByte(258), equalTo((byte) 2)); + assertThat(numberConverter.getByte(-1), equalTo((byte) -1)); + } + + @Test + public void testByteFromString() { + assertThat(numberConverter.getByte("1"), equalTo((byte) 1)); + assertThat(numberConverter.getByte("-1"), equalTo((byte) -1)); + } + + @Test + public void testShortFromNumber() throws SFException { + assertThat(numberConverter.getShort(1, Types.INTEGER), equalTo((short) 1)); + assertThat(numberConverter.getShort(2.5, Types.DOUBLE), equalTo((short) 2)); + assertThat(numberConverter.getShort(3.4f, Types.FLOAT), equalTo((short) 3)); + } + + @Test + public void testShortFromString() throws SFException { + assertThat(numberConverter.getShort("1", Types.INTEGER), equalTo((short) 1)); + assertThat(numberConverter.getShort("2.5", Types.DOUBLE), equalTo((short) 2)); + assertThat(numberConverter.getShort("3.4", Types.FLOAT), equalTo((short) 3)); + assertThat(numberConverter.getShort("4.5.6", Types.FLOAT), equalTo((short) 4)); + } + + @Test + public void testIntFromNumber() throws SFException { + assertThat(numberConverter.getInt(1, Types.INTEGER), equalTo(1)); + assertThat(numberConverter.getInt(2.5, Types.DOUBLE), equalTo(2)); + assertThat(numberConverter.getInt(3.4f, Types.FLOAT), equalTo(3)); + } + + @Test + public void testIntFromString() throws SFException { + assertThat(numberConverter.getInt("1", Types.INTEGER), equalTo(1)); + assertThat(numberConverter.getInt("2.5", Types.DOUBLE), equalTo(2)); + assertThat(numberConverter.getInt("3.4", Types.FLOAT), equalTo(3)); + assertThat(numberConverter.getInt("4.5.6", Types.FLOAT), equalTo(4)); + } + + @Test + public void testLongFromNumber() throws SFException { + assertThat(numberConverter.getLong(1, Types.INTEGER), equalTo(1L)); + assertThat(numberConverter.getLong(2.5, Types.DOUBLE), equalTo(2L)); + assertThat(numberConverter.getLong(3.4f, Types.FLOAT), equalTo(3L)); + } + + @Test + public void testLongFromString() throws SFException { + assertThat(numberConverter.getLong("1", Types.INTEGER), equalTo(1L)); + assertThat(numberConverter.getLong("2.5", Types.DOUBLE), equalTo(2L)); + assertThat(numberConverter.getLong("3.4", Types.FLOAT), equalTo(3L)); + assertThat(numberConverter.getLong("4.5.6", Types.FLOAT), equalTo(4L)); + } + + @Test + public void testBigDecimalFromNumber() throws SFException { + assertThat(numberConverter.getBigDecimal(1, Types.INTEGER), equalTo(BigDecimal.ONE)); + assertThat(numberConverter.getBigDecimal(1, Types.BIGINT), equalTo(BigDecimal.ONE)); + assertThat(numberConverter.getBigDecimal(1.5, Types.FLOAT), equalTo(new BigDecimal("1.5"))); + } + + @Test + public void testBigDecimalFromString() throws SFException { + assertThat(numberConverter.getBigDecimal("1", Types.INTEGER), equalTo(BigDecimal.ONE)); + assertThat(numberConverter.getBigDecimal("1", Types.BIGINT), equalTo(BigDecimal.ONE)); + assertThat(numberConverter.getBigDecimal("1.5", Types.FLOAT), equalTo(new BigDecimal("1.5"))); + } + + @Test + public void testBigDecimalWithScaleFromNumber() throws SFException { + assertThat(numberConverter.getBigDecimal(1, Types.INTEGER), equalTo(BigDecimal.ONE)); + assertThat(numberConverter.getBigDecimal(1, Types.BIGINT), equalTo(BigDecimal.ONE)); + assertThat(numberConverter.getBigDecimal(1.5, Types.FLOAT), equalTo(new BigDecimal("1.5"))); + assertThat( + numberConverter.getBigDecimal(1.50001, Types.FLOAT, 1), equalTo(new BigDecimal("1.5"))); + assertThat( + numberConverter.getBigDecimal(1.50001, Types.FLOAT, 5), equalTo(new BigDecimal("1.50001"))); + } + + @Test + public void testBigDecimalWithScaleFromString() throws SFException { + assertThat(numberConverter.getBigDecimal("1", Types.INTEGER), equalTo(BigDecimal.ONE)); + assertThat(numberConverter.getBigDecimal("1", Types.BIGINT), equalTo(BigDecimal.ONE)); + assertThat(numberConverter.getBigDecimal("1.5", Types.FLOAT), equalTo(new BigDecimal("1.5"))); + assertThat( + numberConverter.getBigDecimal("1.50001", Types.FLOAT, 1), equalTo(new BigDecimal("1.5"))); + assertThat( + numberConverter.getBigDecimal("1.50001", Types.FLOAT, 5), + equalTo(new BigDecimal("1.50001"))); + } + + @Test + public void testFloatFromNumber() throws SFException { + assertThat(numberConverter.getFloat(1, Types.INTEGER), equalTo(1.0f)); + assertThat(numberConverter.getFloat(1, Types.BIGINT), equalTo(1.0f)); + assertThat(numberConverter.getFloat(1.5, Types.FLOAT), equalTo(1.5f)); + assertThat(numberConverter.getFloat(1.5, Types.DOUBLE), equalTo(1.5f)); + } + + @Test + public void testFloatFromString() throws SFException { + assertThat(numberConverter.getFloat("1", Types.INTEGER), equalTo(1.0f)); + assertThat(numberConverter.getFloat("1", Types.BIGINT), equalTo(1.0f)); + assertThat(numberConverter.getFloat("1.5", Types.FLOAT), equalTo(1.5f)); + assertThat(numberConverter.getFloat("1.5", Types.DOUBLE), equalTo(1.5f)); + assertThat(numberConverter.getFloat("inf", Types.FLOAT), equalTo(Float.POSITIVE_INFINITY)); + assertThat(numberConverter.getFloat("-inf", Types.FLOAT), equalTo(Float.NEGATIVE_INFINITY)); + } + + @Test + public void testDoubleFromNumber() throws SFException { + assertThat(numberConverter.getDouble(1, Types.INTEGER), equalTo(1.0)); + assertThat(numberConverter.getDouble(1, Types.BIGINT), equalTo(1.0)); + assertThat(numberConverter.getDouble(1.5, Types.FLOAT), equalTo(1.5)); + assertThat(numberConverter.getDouble(1.5, Types.DOUBLE), equalTo(1.5)); + } + + @Test + public void testDoubleFromString() throws SFException { + assertThat(numberConverter.getDouble("1", Types.INTEGER), equalTo(1.0)); + assertThat(numberConverter.getDouble("1", Types.BIGINT), equalTo(1.0)); + assertThat(numberConverter.getDouble("1.5", Types.FLOAT), equalTo(1.5)); + assertThat(numberConverter.getDouble("1.5", Types.DOUBLE), equalTo(1.5)); + assertThat(numberConverter.getDouble("inf", Types.FLOAT), equalTo(Double.POSITIVE_INFINITY)); + assertThat(numberConverter.getDouble("-inf", Types.FLOAT), equalTo(Double.NEGATIVE_INFINITY)); + } + + @Test + public void testBigIntFromNumber() throws SFException { + assertThat(numberConverter.getBigInt(1, Types.BIGINT), equalTo(1L)); + assertThat( + numberConverter.getBigInt(9_223_372_036_854_775_807L, Types.BIGINT), + equalTo(9_223_372_036_854_775_807L)); + } + + @Test + public void testBigIntFromString() throws SFException { + assertThat(numberConverter.getBigInt("1", Types.BIGINT), equalTo(1L)); + assertThat( + numberConverter.getBigInt("9223372036854775807", Types.BIGINT), + equalTo(9_223_372_036_854_775_807L)); + assertThat( + numberConverter.getBigInt("9223372036854775808", Types.BIGINT), + equalTo(new BigDecimal("9223372036854775808"))); + } +} diff --git a/src/test/java/net/snowflake/client/core/json/StringConverterTest.java b/src/test/java/net/snowflake/client/core/json/StringConverterTest.java new file mode 100644 index 000000000..5fe3dd2cb --- /dev/null +++ b/src/test/java/net/snowflake/client/core/json/StringConverterTest.java @@ -0,0 +1,108 @@ +package net.snowflake.client.core.json; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import java.sql.Types; +import java.time.ZoneId; +import java.util.TimeZone; +import net.snowflake.client.core.SFException; +import net.snowflake.client.core.SFSession; +import net.snowflake.client.jdbc.SnowflakeResultSetSerializableV1; +import net.snowflake.client.jdbc.SnowflakeUtil; +import net.snowflake.common.core.SFBinaryFormat; +import net.snowflake.common.core.SnowflakeDateTimeFormat; +import org.junit.Before; +import org.junit.Test; + +public class StringConverterTest { + private final TimeZone honoluluTimeZone = + TimeZone.getTimeZone(ZoneId.of("America/Nuuk")); // session time zone + private final SnowflakeResultSetSerializableV1 resultSetSerializableV1 = + mock(SnowflakeResultSetSerializableV1.class); + private Converters converters; + + private StringConverter stringConverter; + + @Before + public void init() { + SnowflakeDateTimeFormat timestampNTZFormatter = + SnowflakeDateTimeFormat.fromSqlFormat("YYYY-MM-DD HH24:MI:SS.FF3"); + SnowflakeDateTimeFormat timestampLTZFormatter = + SnowflakeDateTimeFormat.fromSqlFormat("YYYY/DD/MM HH24:MI:SS.FF3"); + SnowflakeDateTimeFormat timestampTZFormatter = + SnowflakeDateTimeFormat.fromSqlFormat("YYYY MM DD HH24:MI:SS.FF3"); + SnowflakeDateTimeFormat dateFormatter = SnowflakeDateTimeFormat.fromSqlFormat("YYYY-MM-DD"); + SnowflakeDateTimeFormat timeFormatter = SnowflakeDateTimeFormat.fromSqlFormat("HH24:MI:SS.FF3"); + converters = + new Converters( + honoluluTimeZone, + new SFSession(), + 1, + false, + false, + false, + false, + SFBinaryFormat.BASE64, + dateFormatter, + timeFormatter, + timestampNTZFormatter, + timestampLTZFormatter, + timestampTZFormatter); + stringConverter = converters.getStringConverter(); + } + + @Test + public void testConvertingString() throws SFException { + assertEquals("test", stringConverter.getString("test", Types.VARCHAR, Types.VARCHAR, 0)); + } + + @Test + public void testConvertingBoolean() throws SFException { + assertEquals("TRUE", stringConverter.getString(true, Types.BOOLEAN, Types.BOOLEAN, 0)); + assertEquals("TRUE", stringConverter.getString("true", Types.BOOLEAN, Types.BOOLEAN, 0)); + assertEquals("FALSE", stringConverter.getString(false, Types.BOOLEAN, Types.BOOLEAN, 0)); + assertEquals("FALSE", stringConverter.getString("false", Types.BOOLEAN, Types.BOOLEAN, 0)); + } + + @Test + public void testConvertingNumbers() throws SFException { + assertEquals("12", stringConverter.getString(12, Types.INTEGER, Types.INTEGER, 0)); + assertEquals("12", stringConverter.getString(12, Types.TINYINT, Types.TINYINT, 0)); + assertEquals("12", stringConverter.getString(12, Types.SMALLINT, Types.SMALLINT, 0)); + assertEquals("12", stringConverter.getString(12L, Types.BIGINT, Types.BIGINT, 0)); + assertEquals("12.5", stringConverter.getString(12.5, Types.DOUBLE, Types.DOUBLE, 0)); + assertEquals("12.5", stringConverter.getString(12.5F, Types.FLOAT, Types.FLOAT, 0)); + } + + @Test + public void testConvertingTimestamp() throws SFException { + assertEquals( + "1988-03-21 22:33:15.000", + stringConverter.getString("574986795", Types.TIMESTAMP, Types.TIMESTAMP, 0)); + assertEquals( + "1988-03-21 19:33:15.000", + stringConverter.getString( + "574986795", Types.TIMESTAMP, SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ, 0)); + assertEquals( + "1988-03-21 14:33:15.000", + stringConverter.getString( + "574986795 960", Types.TIMESTAMP, SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ, 0)); + } + + @Test + public void testConvertingDate() throws SFException { + assertEquals("2023-12-18", stringConverter.getString("19709", Types.DATE, Types.DATE, 0)); + } + + @Test + public void testConvertingTime() throws SFException { + assertEquals( + "00:13:18.000", stringConverter.getString("798.838000000", Types.TIME, Types.TIME, 0)); + } + + @Test + public void testConvertingBinary() throws SFException { + assertEquals("AQID", stringConverter.getString("010203", Types.BINARY, Types.BINARY, 0)); + } +} diff --git a/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java b/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java index 3c10381be..18ad2eb8c 100644 --- a/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java +++ b/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java @@ -17,6 +17,7 @@ import java.util.stream.IntStream; import net.snowflake.client.category.TestCategoryConnection; import net.snowflake.client.core.*; +import net.snowflake.client.core.json.Converters; import net.snowflake.client.jdbc.telemetry.Telemetry; import net.snowflake.client.jdbc.telemetry.TelemetryData; import net.snowflake.common.core.SnowflakeDateTimeFormat; @@ -598,6 +599,7 @@ public String[] getChildQueryIds(String queryID) throws SQLException { } private static class MockJsonResultSet extends SFJsonResultSet { + private static final TimeZone defaultTimeZone = TimeZone.getTimeZone("America/Los_Angeles"); JsonNode resultJson; int currentRowIdx = -1; @@ -605,6 +607,22 @@ private static class MockJsonResultSet extends SFJsonResultSet { public MockJsonResultSet(JsonNode mockedJsonResponse, MockSnowflakeSFSession sfSession) throws SnowflakeSQLException { + super( + defaultTimeZone, + new Converters( + defaultTimeZone, + new SFSession(), + 1, + true, + false, + false, + false, + null, + null, + null, + null, + null, + null)); setSession(sfSession); this.resultJson = mockedJsonResponse.path("data").path("rowset"); this.resultSetMetaData = MockConnectionTest.getRSMDFromResponse(mockedJsonResponse, session);