diff --git a/src/main/java/net/snowflake/client/core/ArrowSqlInput.java b/src/main/java/net/snowflake/client/core/ArrowSqlInput.java new file mode 100644 index 000000000..c63291f34 --- /dev/null +++ b/src/main/java/net/snowflake/client/core/ArrowSqlInput.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2012-2024 Snowflake Computing Inc. All rights reserved. + */ + +package net.snowflake.client.core; + +import static net.snowflake.client.jdbc.SnowflakeUtil.mapExceptions; + +import java.math.BigDecimal; +import java.sql.Date; +import java.sql.SQLData; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Iterator; +import java.util.List; +import java.util.TimeZone; +import net.snowflake.client.core.json.Converters; +import net.snowflake.client.core.structs.SQLDataCreationHelper; +import net.snowflake.client.jdbc.FieldMetadata; +import net.snowflake.client.util.ThrowingBiFunction; +import org.apache.arrow.vector.util.JsonStringHashMap; + +@SnowflakeJdbcInternalApi +public class ArrowSqlInput extends BaseSqlInput { + + private final Iterator structuredTypeFields; + private int currentIndex = 0; + + public ArrowSqlInput( + JsonStringHashMap input, + SFBaseSession session, + Converters converters, + List fields) { + super(session, converters, fields); + this.structuredTypeFields = input.values().iterator(); + } + + @Override + public String readString() throws SQLException { + return withNextValue( + ((value, fieldMetadata) -> { + int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session); + int columnSubType = fieldMetadata.getType(); + int scale = fieldMetadata.getScale(); + return mapExceptions( + () -> + converters + .getStringConverter() + .getString(value, columnType, columnSubType, scale)); + })); + } + + @Override + public boolean readBoolean() throws SQLException { + return withNextValue( + (value, fieldMetadata) -> { + int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session); + return mapExceptions( + () -> converters.getBooleanConverter().getBoolean(value, columnType)); + }); + } + + @Override + public byte readByte() throws SQLException { + return withNextValue( + (value, fieldMetadata) -> + mapExceptions(() -> converters.getNumberConverter().getByte(value))); + } + + @Override + public short readShort() throws SQLException { + return withNextValue( + (value, fieldMetadata) -> { + int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session); + return mapExceptions(() -> converters.getNumberConverter().getShort(value, columnType)); + }); + } + + @Override + public int readInt() throws SQLException { + return withNextValue( + (value, fieldMetadata) -> { + int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session); + return mapExceptions(() -> converters.getNumberConverter().getInt(value, columnType)); + }); + } + + @Override + public long readLong() throws SQLException { + return withNextValue( + (value, fieldMetadata) -> { + int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session); + return mapExceptions(() -> converters.getNumberConverter().getLong(value, columnType)); + }); + } + + @Override + public float readFloat() throws SQLException { + return withNextValue( + (value, fieldMetadata) -> { + int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session); + return mapExceptions(() -> converters.getNumberConverter().getFloat(value, columnType)); + }); + } + + @Override + public double readDouble() throws SQLException { + return withNextValue( + (value, fieldMetadata) -> { + int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session); + return mapExceptions(() -> converters.getNumberConverter().getDouble(value, columnType)); + }); + } + + @Override + public BigDecimal readBigDecimal() throws SQLException { + return withNextValue( + (value, fieldMetadata) -> { + int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session); + return mapExceptions( + () -> converters.getNumberConverter().getBigDecimal(value, columnType)); + }); + } + + @Override + public byte[] readBytes() throws SQLException { + return withNextValue( + (value, fieldMetadata) -> { + int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session); + int columnSubType = fieldMetadata.getType(); + int scale = fieldMetadata.getScale(); + return mapExceptions( + () -> + converters.getBytesConverter().getBytes(value, columnType, columnSubType, scale)); + }); + } + + @Override + public Date readDate() throws SQLException { + return withNextValue( + (value, fieldMetadata) -> + mapExceptions( + () -> + converters + .getStructuredTypeDateTimeConverter() + .getDate((int) value, TimeZone.getDefault()))); + } + + @Override + public Time readTime() throws SQLException { + return withNextValue( + (value, fieldMetadata) -> + mapExceptions( + () -> { + int scale = fieldMetadata.getScale(); + return converters + .getStructuredTypeDateTimeConverter() + .getTime((long) value, scale); + })); + } + + @Override + public Timestamp readTimestamp(TimeZone tz) throws SQLException { + return withNextValue( + (value, fieldMetadata) -> { + if (value == null) { + return null; + } + int scale = fieldMetadata.getScale(); + return mapExceptions( + () -> + converters + .getStructuredTypeDateTimeConverter() + .getTimestamp( + (JsonStringHashMap) value, + fieldMetadata.getBase(), + tz, + scale)); + }); + } + + @Override + public Object readObject() throws SQLException { + return withNextValue( + (value, fieldMetadata) -> { + if (!(value instanceof JsonStringHashMap)) { + throw new SQLException( + "Invalid value passed to 'readObject()', expected Map; got: " + value.getClass()); + } + return value; + }); + } + + @Override + public T readObject(Class type) throws SQLException { + return withNextValue( + (value, fieldMetadata) -> { + SQLData instance = (SQLData) SQLDataCreationHelper.create(type); + instance.readSQL( + new ArrowSqlInput( + (JsonStringHashMap) value, + session, + converters, + fieldMetadata.getFields()), + null); + return (T) instance; + }); + } + + private T withNextValue(ThrowingBiFunction action) + throws SQLException { + return action.apply(structuredTypeFields.next(), fields.get(currentIndex++)); + } +} diff --git a/src/main/java/net/snowflake/client/core/BaseSqlInput.java b/src/main/java/net/snowflake/client/core/BaseSqlInput.java new file mode 100644 index 000000000..b0f81167b --- /dev/null +++ b/src/main/java/net/snowflake/client/core/BaseSqlInput.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2012-2024 Snowflake Computing Inc. All rights reserved. + */ + +package net.snowflake.client.core; + +import java.io.InputStream; +import java.io.Reader; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.sql.Timestamp; +import java.util.List; +import net.snowflake.client.core.json.Converters; +import net.snowflake.client.jdbc.FieldMetadata; +import net.snowflake.client.jdbc.SnowflakeLoggedFeatureNotSupportedException; + +@SnowflakeJdbcInternalApi +public abstract class BaseSqlInput implements SFSqlInput { + + protected final SFBaseSession session; + protected final Converters converters; + protected final List fields; + + protected BaseSqlInput(SFBaseSession session, Converters converters, List fields) { + this.session = session; + this.converters = converters; + this.fields = fields; + } + + @Override + public Timestamp readTimestamp() throws SQLException { + return readTimestamp(null); + } + + @Override + public Reader readCharacterStream() throws SQLException { + throw new SnowflakeLoggedFeatureNotSupportedException(session, "readCharacterStream"); + } + + @Override + public InputStream readAsciiStream() throws SQLException { + throw new SnowflakeLoggedFeatureNotSupportedException(session, "readAsciiStream"); + } + + @Override + public InputStream readBinaryStream() throws SQLException { + throw new SnowflakeLoggedFeatureNotSupportedException(session, "readBinaryStream"); + } + + @Override + public Ref readRef() throws SQLException { + throw new SnowflakeLoggedFeatureNotSupportedException(session, "readRef"); + } + + @Override + public Blob readBlob() throws SQLException { + throw new SnowflakeLoggedFeatureNotSupportedException(session, "readBlob"); + } + + @Override + public Clob readClob() throws SQLException { + throw new SnowflakeLoggedFeatureNotSupportedException(session, "readClob"); + } + + @Override + public Array readArray() throws SQLException { + throw new SnowflakeLoggedFeatureNotSupportedException(session, "readArray"); + } + + @Override + public boolean wasNull() throws SQLException { + return false; // nulls are not allowed in structure types + } + + @Override + public URL readURL() throws SQLException { + throw new SnowflakeLoggedFeatureNotSupportedException(session, "readCharacterStream"); + } + + @Override + public NClob readNClob() throws SQLException { + throw new SnowflakeLoggedFeatureNotSupportedException(session, "readNClob"); + } + + @Override + public String readNString() throws SQLException { + throw new SnowflakeLoggedFeatureNotSupportedException(session, "readNString"); + } + + @Override + public SQLXML readSQLXML() throws SQLException { + throw new SnowflakeLoggedFeatureNotSupportedException(session, "readSQLXML"); + } + + @Override + public RowId readRowId() throws SQLException { + throw new SnowflakeLoggedFeatureNotSupportedException(session, "readRowId"); + } +} diff --git a/src/main/java/net/snowflake/client/core/JsonSqlInput.java b/src/main/java/net/snowflake/client/core/JsonSqlInput.java index f82e492a1..f5c4bd6f0 100644 --- a/src/main/java/net/snowflake/client/core/JsonSqlInput.java +++ b/src/main/java/net/snowflake/client/core/JsonSqlInput.java @@ -3,24 +3,15 @@ */ package net.snowflake.client.core; +import static net.snowflake.client.jdbc.SnowflakeUtil.mapExceptions; + import com.fasterxml.jackson.databind.JsonNode; -import java.io.InputStream; -import java.io.Reader; import java.math.BigDecimal; -import java.net.URL; -import java.sql.Array; -import java.sql.Blob; -import java.sql.Clob; import java.sql.Date; -import java.sql.NClob; -import java.sql.Ref; -import java.sql.RowId; import java.sql.SQLData; import java.sql.SQLException; -import java.sql.SQLXML; import java.sql.Time; import java.sql.Timestamp; -import java.sql.Types; import java.time.Instant; import java.time.ZoneOffset; import java.util.Iterator; @@ -30,28 +21,27 @@ import net.snowflake.client.core.structs.SQLDataCreationHelper; import net.snowflake.client.jdbc.FieldMetadata; import net.snowflake.client.jdbc.SnowflakeLoggedFeatureNotSupportedException; -import net.snowflake.client.jdbc.SnowflakeUtil; -import net.snowflake.client.util.ThrowingCallable; import net.snowflake.client.util.ThrowingTriFunction; import net.snowflake.common.core.SFTimestamp; import net.snowflake.common.core.SnowflakeDateTimeFormat; @SnowflakeJdbcInternalApi -public class JsonSqlInput implements SFSqlInput { +public class JsonSqlInput extends BaseSqlInput { private final JsonNode input; private final Iterator elements; - private final SFBaseSession session; - private final Converters converters; - private final List fields; + private final TimeZone sessionTimeZone; private int currentIndex = 0; public JsonSqlInput( - JsonNode input, SFBaseSession session, Converters converters, List fields) { + JsonNode input, + SFBaseSession session, + Converters converters, + List fields, + TimeZone sessionTimeZone) { + super(session, converters, fields); this.input = input; this.elements = input.elements(); - this.session = session; - this.converters = converters; - this.fields = fields; + this.sessionTimeZone = sessionTimeZone; } public JsonNode getInput() { @@ -180,11 +170,6 @@ public Time readTime() throws SQLException { }); } - @Override - public Timestamp readTimestamp() throws SQLException { - return readTimestamp(null); - } - @Override public Timestamp readTimestamp(TimeZone tz) throws SQLException { return withNextValue( @@ -196,7 +181,8 @@ public Timestamp readTimestamp(TimeZone tz) throws SQLException { int columnSubType = fieldMetadata.getType(); int scale = fieldMetadata.getScale(); Timestamp result = - SnowflakeUtil.getTimestampFromType(columnSubType, (String) value, session); + SqlInputTimestampUtil.getTimestampFromType( + columnSubType, (String) value, session, sessionTimeZone, tz); if (result != null) { return result; } @@ -208,106 +194,21 @@ public Timestamp readTimestamp(TimeZone tz) throws SQLException { }); } - private Timestamp getTimestampFromType(int columnSubType, String value) { - if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ) { - return getTimestampFromFormat("TIMESTAMP_LTZ_OUTPUT_FORMAT", value); - } else if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_NTZ - || columnSubType == Types.TIMESTAMP) { - return getTimestampFromFormat("TIMESTAMP_NTZ_OUTPUT_FORMAT", value); - } else if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ) { - return getTimestampFromFormat("TIMESTAMP_TZ_OUTPUT_FORMAT", value); - } else { - return null; - } - } - - private Timestamp getTimestampFromFormat(String format, String value) { - String rawFormat = (String) session.getCommonParameters().get(format); - if (rawFormat == null || rawFormat.isEmpty()) { - rawFormat = (String) session.getCommonParameters().get("TIMESTAMP_OUTPUT_FORMAT"); - } - SnowflakeDateTimeFormat formatter = SnowflakeDateTimeFormat.fromSqlFormat(rawFormat); - return formatter.parse(value).getTimestamp(); - } - - @Override - public Reader readCharacterStream() throws SQLException { - throw new SnowflakeLoggedFeatureNotSupportedException(session, "readCharacterStream"); - } - - @Override - public InputStream readAsciiStream() throws SQLException { - throw new SnowflakeLoggedFeatureNotSupportedException(session, "readAsciiStream"); - } - - @Override - public InputStream readBinaryStream() throws SQLException { - throw new SnowflakeLoggedFeatureNotSupportedException(session, "readBinaryStream"); - } - @Override public Object readObject() throws SQLException { // TODO structuredType return map - SNOW-974575 throw new SnowflakeLoggedFeatureNotSupportedException(session, "readObject"); } - @Override - public Ref readRef() throws SQLException { - throw new SnowflakeLoggedFeatureNotSupportedException(session, "readRef"); - } - - @Override - public Blob readBlob() throws SQLException { - throw new SnowflakeLoggedFeatureNotSupportedException(session, "readBlob"); - } - - @Override - public Clob readClob() throws SQLException { - throw new SnowflakeLoggedFeatureNotSupportedException(session, "readClob"); - } - - @Override - public Array readArray() throws SQLException { - throw new SnowflakeLoggedFeatureNotSupportedException(session, "readArray"); - } - - @Override - public boolean wasNull() throws SQLException { - return false; // nulls are not allowed in structure types - } - - @Override - public URL readURL() throws SQLException { - throw new SnowflakeLoggedFeatureNotSupportedException(session, "readURL"); - } - - @Override - public NClob readNClob() throws SQLException { - throw new SnowflakeLoggedFeatureNotSupportedException(session, "readNClob"); - } - - @Override - public String readNString() throws SQLException { - throw new SnowflakeLoggedFeatureNotSupportedException(session, "readNString"); - } - - @Override - public SQLXML readSQLXML() throws SQLException { - throw new SnowflakeLoggedFeatureNotSupportedException(session, "readSQLXML"); - } - - @Override - public RowId readRowId() throws SQLException { - throw new SnowflakeLoggedFeatureNotSupportedException(session, "readRowId"); - } - @Override public T readObject(Class type) throws SQLException { return withNextValue( (__, jsonNode, fieldMetadata) -> { SQLData instance = (SQLData) SQLDataCreationHelper.create(type); instance.readSQL( - new JsonSqlInput(jsonNode, session, converters, fieldMetadata.getFields()), null); + new JsonSqlInput( + jsonNode, session, converters, fieldMetadata.getFields(), sessionTimeZone), + null); return (T) instance; }); } @@ -331,14 +232,6 @@ private Object getValue(JsonNode jsonNode) { return null; } - private T mapExceptions(ThrowingCallable action) throws SQLException { - try { - return action.call(); - } catch (SFException e) { - throw new SQLException(e); - } - } - private static SnowflakeDateTimeFormat getFormat(SFBaseSession session, String format) { return SnowflakeDateTimeFormat.fromSqlFormat( (String) session.getCommonParameters().get(format)); diff --git a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java index f7cb5df93..daf60b804 100644 --- a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java @@ -21,6 +21,8 @@ import java.sql.Types; import java.util.TimeZone; import net.snowflake.client.core.arrow.ArrowVectorConverter; +import net.snowflake.client.core.arrow.StructConverter; +import net.snowflake.client.core.arrow.VarCharConverter; import net.snowflake.client.core.json.Converters; import net.snowflake.client.core.structs.StructureTypeHelper; import net.snowflake.client.jdbc.ArrowResultChunk; @@ -39,6 +41,7 @@ import net.snowflake.common.core.SnowflakeDateTimeFormat; import net.snowflake.common.core.SqlState; import org.apache.arrow.memory.RootAllocator; +import org.apache.arrow.vector.util.JsonStringHashMap; /** Arrow result set implementation */ public class SFArrowResultSet extends SFBaseResultSet implements DataConversionContext { @@ -65,9 +68,6 @@ public class SFArrowResultSet extends SFBaseResultSet implements DataConversionC /** is array bind supported */ private final boolean arrayBindSupported; - /** session timezone */ - private TimeZone timeZone; - /** index of next chunk to consume */ private long nextChunkIndex = 0; @@ -186,7 +186,6 @@ public SFArrowResultSet( this.parameters = resultSetSerializable.getParameters(); this.chunkCount = resultSetSerializable.getChunkFileCount(); this.chunkDownloader = resultSetSerializable.getChunkDownloader(); - this.timeZone = resultSetSerializable.getTimeZone(); this.honorClientTZForTimestampNTZ = resultSetSerializable.isHonorClientTZForTimestampNTZ(); this.resultVersion = resultSetSerializable.getResultVersion(); this.numberOfBinds = resultSetSerializable.getNumberOfBinds(); @@ -199,8 +198,7 @@ public SFArrowResultSet( this.timestampTZFormatter = resultSetSerializable.getTimestampTZFormatter(); this.dateFormatter = resultSetSerializable.getDateFormatter(); this.timeFormatter = resultSetSerializable.getTimeFormatter(); - this.timeZone = resultSetSerializable.getTimeZone(); - this.honorClientTZForTimestampNTZ = resultSetSerializable.isHonorClientTZForTimestampNTZ(); + this.sessionTimezone = resultSetSerializable.getTimeZone(); this.binaryFormatter = resultSetSerializable.getBinaryFormatter(); this.resultSetMetaData = resultSetSerializable.getSFResultSetMetaData(); this.treatNTZAsUTC = resultSetSerializable.getTreatNTZAsUTC(); @@ -473,7 +471,7 @@ public Date getDate(int columnIndex, TimeZone tz) throws SFException { ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1); int index = currentChunkIterator.getCurrentRowInRecordBatch(); wasNull = converter.isNull(index); - converter.setSessionTimeZone(timeZone); + converter.setSessionTimeZone(sessionTimezone); converter.setUseSessionTimezone(useSessionTimezone); return converter.toDate(index, tz, resultSetSerializable.getFormatDateWithTimeZone()); } @@ -483,7 +481,7 @@ public Time getTime(int columnIndex) throws SFException { ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1); int index = currentChunkIterator.getCurrentRowInRecordBatch(); wasNull = converter.isNull(index); - converter.setSessionTimeZone(timeZone); + converter.setSessionTimeZone(sessionTimezone); converter.setUseSessionTimezone(useSessionTimezone); return converter.toTime(index); } @@ -492,7 +490,7 @@ public Time getTime(int columnIndex) throws SFException { public Timestamp getTimestamp(int columnIndex, TimeZone tz) throws SFException { ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1); int index = currentChunkIterator.getCurrentRowInRecordBatch(); - converter.setSessionTimeZone(timeZone); + converter.setSessionTimeZone(sessionTimezone); converter.setUseSessionTimezone(useSessionTimezone); wasNull = converter.isNull(index); return converter.toTimestamp(index, tz); @@ -505,9 +503,39 @@ public Object getObject(int columnIndex) throws SFException { wasNull = converter.isNull(index); converter.setTreatNTZAsUTC(treatNTZAsUTC); converter.setUseSessionTimezone(useSessionTimezone); - converter.setSessionTimeZone(timeZone); + converter.setSessionTimeZone(sessionTimezone); Object obj = converter.toObject(index); - return handleObjectType(columnIndex, obj); + int type = resultSetMetaData.getColumnType(columnIndex); + if (type == Types.STRUCT && StructureTypeHelper.isStructureTypeEnabled()) { + if (converter instanceof VarCharConverter) { + return createJsonSqlInput(columnIndex, obj); + } else if (converter instanceof StructConverter) { + return createArrowSqlInput(columnIndex, (JsonStringHashMap) obj); + } + } + return obj; + } + + private Object createJsonSqlInput(int columnIndex, Object obj) throws SFException { + try { + JsonNode jsonNode = OBJECT_MAPPER.readTree((String) obj); + return new JsonSqlInput( + jsonNode, + session, + jsonConverters, + resultSetMetaData.getColumnMetadata().get(columnIndex - 1).getFields(), + sessionTimezone); + } catch (JsonProcessingException e) { + throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); + } + } + + private Object createArrowSqlInput(int columnIndex, JsonStringHashMap input) { + return new ArrowSqlInput( + input, + session, + jsonConverters, + resultSetMetaData.getColumnMetadata().get(columnIndex - 1).getFields()); } @Override @@ -516,29 +544,12 @@ public Array getArray(int columnIndex) throws SFException { throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, "data type ARRAY"); } - private Object handleObjectType(int columnIndex, Object obj) throws SFException { - int columnType = resultSetMetaData.getColumnType(columnIndex); - if (columnType == Types.STRUCT && StructureTypeHelper.isStructureTypeEnabled()) { - try { - JsonNode jsonNode = OBJECT_MAPPER.readTree((String) obj); - return new JsonSqlInput( - jsonNode, - session, - jsonConverters, - resultSetMetaData.getColumnMetadata().get(columnIndex - 1).getFields()); - } catch (JsonProcessingException e) { - throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); - } - } - return obj; - } - @Override public BigDecimal getBigDecimal(int columnIndex) throws SFException { ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1); int index = currentChunkIterator.getCurrentRowInRecordBatch(); wasNull = converter.isNull(index); - converter.setSessionTimeZone(timeZone); + converter.setSessionTimeZone(sessionTimezone); converter.setUseSessionTimezone(useSessionTimezone); return converter.toBigDecimal(index); } @@ -678,7 +689,7 @@ public int getScale(int columnIndex) { @Override public TimeZone getTimeZone() { - return timeZone; + return sessionTimezone; } @Override diff --git a/src/main/java/net/snowflake/client/core/SFBaseResultSet.java b/src/main/java/net/snowflake/client/core/SFBaseResultSet.java index e765b87ba..f81c5338b 100644 --- a/src/main/java/net/snowflake/client/core/SFBaseResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFBaseResultSet.java @@ -64,6 +64,8 @@ public abstract class SFBaseResultSet { // result set protected SnowflakeResultSetSerializableV1 resultSetSerializable; + protected TimeZone sessionTimezone; + public abstract boolean isLast(); public abstract boolean isAfterLast(); @@ -140,6 +142,10 @@ public SFResultSetMetaData getMetaData() { return resultSetMetaData; } + public TimeZone getSessionTimezone() { + return sessionTimezone; + } + public int getRow() throws SQLException { return row; } diff --git a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java index 15a532f76..d8955bdeb 100644 --- a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java @@ -113,6 +113,18 @@ public Object getObject(int columnIndex) throws SFException { } } + /** + * Sometimes large BIGINTS overflow the java Long type. In these cases, return a BigDecimal type + * instead. + * + * @param columnIndex the column index + * @return an object of type long or BigDecimal depending on number size + * @throws SFException + */ + private Object getBigInt(int columnIndex, Object obj) throws SFException { + return converters.getNumberConverter().getBigInt(obj, columnIndex); + } + @Override public Array getArray(int columnIndex) throws SFException { Object obj = getObjectInternal(columnIndex); @@ -264,7 +276,8 @@ private Object getSqlInput(String input, int columnIndex) throws SFException { jsonNode, session, converters, - resultSetMetaData.getColumnMetadata().get(columnIndex - 1).getFields()); + resultSetMetaData.getColumnMetadata().get(columnIndex - 1).getFields(), + sessionTimeZone); } catch (JsonProcessingException e) { throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); } @@ -343,7 +356,8 @@ private SfSqlArray getArrayInternal(String obj, int columnIndex) throws SFExcept columnSubType, getStream( nodeElements, - converters.timestampConverter(columnSubType, columnType, scale, session)) + converters.timestampConverter( + columnSubType, columnType, scale, session, null, sessionTimezone)) .toArray(Timestamp[]::new)); case Types.BOOLEAN: return new SfSqlArray( @@ -412,16 +426,4 @@ private static Object convert(JsonStringToTypeConverter converter, JsonNode node return converter.convert(node.toString()); } } - - /** - * Sometimes large BIGINTS overflow the java Long type. In these cases, return a BigDecimal type - * instead. - * - * @param columnIndex the column index - * @return an object of type long or BigDecimal depending on number size - * @throws SFException - */ - private Object getBigInt(int columnIndex, Object obj) throws SFException { - return converters.getNumberConverter().getBigInt(obj, columnIndex); - } } diff --git a/src/main/java/net/snowflake/client/core/SqlInputTimestampUtil.java b/src/main/java/net/snowflake/client/core/SqlInputTimestampUtil.java new file mode 100644 index 000000000..b95c518c6 --- /dev/null +++ b/src/main/java/net/snowflake/client/core/SqlInputTimestampUtil.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012-2024 Snowflake Computing Inc. All rights reserved. + */ + +package net.snowflake.client.core; + +import java.sql.Timestamp; +import java.sql.Types; +import java.util.TimeZone; +import net.snowflake.client.jdbc.SnowflakeUtil; +import net.snowflake.common.core.SnowflakeDateTimeFormat; + +@SnowflakeJdbcInternalApi +public class SqlInputTimestampUtil { + + public static Timestamp getTimestampFromType( + int columnSubType, + String value, + SFBaseSession session, + TimeZone sessionTimeZone, + TimeZone tz) { + if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ) { + return getTimestampFromFormat( + "TIMESTAMP_LTZ_OUTPUT_FORMAT", value, session, sessionTimeZone, tz); + } else if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_NTZ + || columnSubType == Types.TIMESTAMP) { + return getTimestampFromFormat( + "TIMESTAMP_NTZ_OUTPUT_FORMAT", value, session, sessionTimeZone, tz); + } else if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ) { + return getTimestampFromFormat( + "TIMESTAMP_TZ_OUTPUT_FORMAT", value, session, sessionTimeZone, tz); + } else { + return null; + } + } + + private static Timestamp getTimestampFromFormat( + String format, String value, SFBaseSession session, TimeZone sessionTimeZone, TimeZone tz) { + String rawFormat = (String) session.getCommonParameters().get(format); + if (rawFormat == null || rawFormat.isEmpty()) { + rawFormat = (String) session.getCommonParameters().get("TIMESTAMP_OUTPUT_FORMAT"); + } + if (tz == null) { + tz = sessionTimeZone; + } + SnowflakeDateTimeFormat formatter = SnowflakeDateTimeFormat.fromSqlFormat(rawFormat); + return formatter.parse(value, tz, 0, false).getTimestamp(); + } +} diff --git a/src/main/java/net/snowflake/client/core/arrow/BigIntToTimeConverter.java b/src/main/java/net/snowflake/client/core/arrow/BigIntToTimeConverter.java index a902cb66a..74d01f98a 100644 --- a/src/main/java/net/snowflake/client/core/arrow/BigIntToTimeConverter.java +++ b/src/main/java/net/snowflake/client/core/arrow/BigIntToTimeConverter.java @@ -44,21 +44,22 @@ public Time toTime(int index) throws SFException { if (isNull(index)) { return null; } else { - SFTime sfTime = toSFTime(index); - if (sfTime == null) { - return null; - } - 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; + long val = bigIntVector.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH); + return getTime(val, context.getScale(columnIndex), useSessionTimezone); + } + } + + public static Time getTime(long value, int scale, boolean useSessionTimezone) throws SFException { + SFTime sfTime = SFTime.fromFractionalSeconds(value, scale); + 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; } @Override diff --git a/src/main/java/net/snowflake/client/core/arrow/BigIntToTimestampLTZConverter.java b/src/main/java/net/snowflake/client/core/arrow/BigIntToTimestampLTZConverter.java index 971b30d9d..236abe553 100644 --- a/src/main/java/net/snowflake/client/core/arrow/BigIntToTimestampLTZConverter.java +++ b/src/main/java/net/snowflake/client/core/arrow/BigIntToTimestampLTZConverter.java @@ -64,14 +64,8 @@ public Timestamp toTimestamp(int index, TimeZone tz) throws SFException { private Timestamp getTimestamp(int index, TimeZone tz) throws SFException { long val = bigIntVector.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH); - int scale = context.getScale(columnIndex); - - Timestamp ts = ArrowResultUtil.toJavaTimestamp(val, scale); - - Timestamp adjustedTimestamp = ResultUtil.adjustTimestamp(ts); - - return adjustedTimestamp; + return getTimestamp(val, scale); } @Override @@ -95,4 +89,9 @@ public boolean toBoolean(int index) throws SFException { ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.BOOLEAN_STR, val); } + + public static Timestamp getTimestamp(long val, int scale) throws SFException { + Timestamp ts = ArrowResultUtil.toJavaTimestamp(val, scale); + return ResultUtil.adjustTimestamp(ts); + } } diff --git a/src/main/java/net/snowflake/client/core/arrow/BigIntToTimestampNTZConverter.java b/src/main/java/net/snowflake/client/core/arrow/BigIntToTimestampNTZConverter.java index dcd601f73..cec64d59e 100644 --- a/src/main/java/net/snowflake/client/core/arrow/BigIntToTimestampNTZConverter.java +++ b/src/main/java/net/snowflake/client/core/arrow/BigIntToTimestampNTZConverter.java @@ -69,19 +69,8 @@ private Timestamp getTimestamp(int index, TimeZone tz, boolean fromToString) thr tz = TimeZone.getDefault(); } long val = bigIntVector.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH); - int scale = context.getScale(columnIndex); - - Timestamp ts = ArrowResultUtil.toJavaTimestamp(val, scale); - - // Note: honorClientTZForTimestampNTZ is not enabled for toString method - if (!fromToString && context.getHonorClientTZForTimestampNTZ()) { - ts = ArrowResultUtil.moveToTimeZone(ts, NTZ, tz); - } - - Timestamp adjustedTimestamp = ResultUtil.adjustTimestamp(ts); - - return adjustedTimestamp; + return getTimestamp(val, tz, scale, context.getHonorClientTZForTimestampNTZ(), fromToString); } @Override @@ -109,4 +98,20 @@ public boolean toBoolean(int index) throws SFException { ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.BOOLEAN_STR, val); } + + public static Timestamp getTimestamp( + long val, TimeZone tz, int scale, boolean honorClientTZForTimestampNTZ, boolean fromToString) + throws SFException { + if (tz == null) { + tz = TimeZone.getDefault(); + } + Timestamp ts = ArrowResultUtil.toJavaTimestamp(val, scale); + + // Note: honorClientTZForTimestampNTZ is not enabled for toString method + if (!fromToString && honorClientTZForTimestampNTZ) { + ts = ArrowResultUtil.moveToTimeZone(ts, NTZ, tz); + } + + return ResultUtil.adjustTimestamp(ts); + } } diff --git a/src/main/java/net/snowflake/client/core/arrow/DateConverter.java b/src/main/java/net/snowflake/client/core/arrow/DateConverter.java index e826591b6..4a6042005 100644 --- a/src/main/java/net/snowflake/client/core/arrow/DateConverter.java +++ b/src/main/java/net/snowflake/client/core/arrow/DateConverter.java @@ -32,11 +32,7 @@ private Date getDate(int index, TimeZone jvmTz, boolean useDateFormat) throws SF return null; } else { int val = dateVector.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH); - if (jvmTz == null || sessionTimeZone == null || !useDateFormat) { - return ArrowResultUtil.getDate(val); - } - // Note: use default time zone to match with current getDate() behavior - return ArrowResultUtil.getDate(val, jvmTz, sessionTimeZone); + return getDate(val, jvmTz, sessionTimeZone, useDateFormat); } } @@ -122,4 +118,14 @@ public boolean toBoolean(int index) throws SFException { ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.BOOLEAN_STR, val); } + + public static Date getDate( + int value, TimeZone jvmTz, TimeZone sessionTimeZone, boolean useDateFormat) + throws SFException { + if (jvmTz == null || sessionTimeZone == null || !useDateFormat) { + return ArrowResultUtil.getDate(value); + } + // Note: use default time zone to match with current getDate() behavior + return ArrowResultUtil.getDate(value, jvmTz, sessionTimeZone); + } } diff --git a/src/main/java/net/snowflake/client/core/arrow/StructConverter.java b/src/main/java/net/snowflake/client/core/arrow/StructConverter.java new file mode 100644 index 000000000..84ccd7c0f --- /dev/null +++ b/src/main/java/net/snowflake/client/core/arrow/StructConverter.java @@ -0,0 +1,28 @@ +package net.snowflake.client.core.arrow; + +import net.snowflake.client.core.DataConversionContext; +import net.snowflake.client.core.SFException; +import net.snowflake.client.core.SnowflakeJdbcInternalApi; +import net.snowflake.client.jdbc.SnowflakeType; +import org.apache.arrow.vector.complex.StructVector; + +@SnowflakeJdbcInternalApi +public class StructConverter extends AbstractArrowVectorConverter { + + private final StructVector structVector; + + public StructConverter(StructVector vector, int columnIndex, DataConversionContext context) { + super(SnowflakeType.OBJECT.name(), vector, columnIndex, context); + structVector = vector; + } + + @Override + public Object toObject(int index) throws SFException { + return structVector.getObject(index); + } + + @Override + public String toString(int index) throws SFException { + return structVector.getObject(index).toString(); + } +} diff --git a/src/main/java/net/snowflake/client/core/arrow/StructuredTypeDateTimeConverter.java b/src/main/java/net/snowflake/client/core/arrow/StructuredTypeDateTimeConverter.java new file mode 100644 index 000000000..cd30c4bf5 --- /dev/null +++ b/src/main/java/net/snowflake/client/core/arrow/StructuredTypeDateTimeConverter.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2012-2024 Snowflake Computing Inc. All rights reserved. + */ + +package net.snowflake.client.core.arrow; + +import static net.snowflake.client.jdbc.SnowflakeType.TIMESTAMP_LTZ; +import static net.snowflake.client.jdbc.SnowflakeType.TIMESTAMP_NTZ; +import static net.snowflake.client.jdbc.SnowflakeType.TIMESTAMP_TZ; + +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.TimeZone; +import net.snowflake.client.core.SFException; +import net.snowflake.client.core.SnowflakeJdbcInternalApi; +import net.snowflake.client.jdbc.ErrorCode; +import net.snowflake.client.jdbc.SnowflakeType; +import org.apache.arrow.vector.util.JsonStringHashMap; + +@SnowflakeJdbcInternalApi +public class StructuredTypeDateTimeConverter { + + private final TimeZone sessionTimeZone; + private final long resultVersion; + private final boolean honorClientTZForTimestampNTZ; + private final boolean treatNTZAsUTC; + private final boolean useSessionTimezone; + private final boolean formatDateWithTimeZone; + + public StructuredTypeDateTimeConverter( + TimeZone sessionTimeZone, + long resultVersion, + boolean honorClientTZForTimestampNTZ, + boolean treatNTZAsUTC, + boolean useSessionTimezone, + boolean formatDateWithTimeZone) { + + this.sessionTimeZone = sessionTimeZone; + this.resultVersion = resultVersion; + this.honorClientTZForTimestampNTZ = honorClientTZForTimestampNTZ; + this.treatNTZAsUTC = treatNTZAsUTC; + this.useSessionTimezone = useSessionTimezone; + this.formatDateWithTimeZone = formatDateWithTimeZone; + } + + public Timestamp getTimestamp( + JsonStringHashMap obj, SnowflakeType type, TimeZone tz, int scale) + throws SFException { + if (tz == null) { + tz = TimeZone.getDefault(); + } + switch (type) { + case TIMESTAMP_LTZ: + return convertTimestampLtz(obj, scale); + case TIMESTAMP_NTZ: + return convertTimestampNtz(obj, tz, scale); + case TIMESTAMP_TZ: + return convertTimestampTz(obj, scale); + } + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, + "Unexpected Arrow Field for " + type.name() + " and object type " + obj.getClass()); + } + + public Date getDate(int value, TimeZone tz) throws SFException { + return DateConverter.getDate(value, tz, sessionTimeZone, formatDateWithTimeZone); + } + + public Time getTime(long value, int scale) throws SFException { + return BigIntToTimeConverter.getTime(value, scale, useSessionTimezone); + } + + private Timestamp convertTimestampLtz(Object obj, int scale) throws SFException { + if (obj instanceof JsonStringHashMap) { + JsonStringHashMap map = (JsonStringHashMap) obj; + if (map.values().size() == 2) { + return TwoFieldStructToTimestampLTZConverter.getTimestamp( + (long) map.get("epoch"), + (int) map.get("fraction"), + sessionTimeZone, + useSessionTimezone, + false); + } + } else if (obj instanceof Long) { + return BigIntToTimestampLTZConverter.getTimestamp((long) obj, scale); + } + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, + "Unexpected Arrow Field for " + TIMESTAMP_LTZ + " and object type " + obj.getClass()); + } + + private Timestamp convertTimestampNtz(Object obj, TimeZone tz, int scale) throws SFException { + if (obj instanceof JsonStringHashMap) { + JsonStringHashMap map = (JsonStringHashMap) obj; + if (map.values().size() == 2) { + return TwoFieldStructToTimestampNTZConverter.getTimestamp( + (long) map.get("epoch"), + (int) map.get("fraction"), + tz, + sessionTimeZone, + treatNTZAsUTC, + useSessionTimezone, + honorClientTZForTimestampNTZ, + false); + } + } else if (obj instanceof Long) { + return BigIntToTimestampNTZConverter.getTimestamp( + (long) obj, tz, scale, honorClientTZForTimestampNTZ, false); + } + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, + "Unexpected Arrow Field for " + TIMESTAMP_NTZ + " and object type " + obj.getClass()); + } + + private Timestamp convertTimestampTz(Object obj, int scale) throws SFException { + if (obj instanceof JsonStringHashMap) { + JsonStringHashMap map = (JsonStringHashMap) obj; + if (map.values().size() == 2) { + return TwoFieldStructToTimestampTZConverter.getTimestamp( + (long) map.get("epoch"), (int) map.get("timezone"), scale); + } else if (map.values().size() == 3) { + return ThreeFieldStructToTimestampTZConverter.getTimestamp( + (long) map.get("epoch"), + (int) map.get("fraction"), + (int) map.get("timezone"), + resultVersion, + useSessionTimezone, + false); + } + } + throw new SFException( + ErrorCode.INVALID_VALUE_CONVERT, + "Unexpected Arrow Field for " + TIMESTAMP_TZ + " and object type " + obj.getClass()); + } +} diff --git a/src/main/java/net/snowflake/client/core/arrow/ThreeFieldStructToTimestampTZConverter.java b/src/main/java/net/snowflake/client/core/arrow/ThreeFieldStructToTimestampTZConverter.java index 4c7f00d53..88d3e53ba 100644 --- a/src/main/java/net/snowflake/client/core/arrow/ThreeFieldStructToTimestampTZConverter.java +++ b/src/main/java/net/snowflake/client/core/arrow/ThreeFieldStructToTimestampTZConverter.java @@ -46,11 +46,10 @@ public boolean isNull(int index) { @Override public String toString(int index) throws SFException { if (context.getTimestampTZFormatter() == null) { - throw new SFException(ErrorCode.INTERNAL_ERROR, "missing timestamp LTZ formatter"); + throw new SFException(ErrorCode.INTERNAL_ERROR, "missing timestamp TZ formatter"); } try { Timestamp ts = epochs.isNull(index) ? null : getTimestamp(index, TimeZone.getDefault(), true); - return ts == null ? null : context.getTimestampTZFormatter().format(ts, timeZone, context.getScale(columnIndex)); @@ -82,23 +81,14 @@ private Timestamp getTimestamp(int index, TimeZone tz, boolean fromToString) thr long epoch = epochs.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH); int fraction = fractions.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH); int timeZoneIndex = timeZoneIndices.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH); - - if (ArrowResultUtil.isTimestampOverflow(epoch)) { - if (fromToString) { - throw new TimestampOperationNotAvailableException(epoch, fraction); - } else { - return null; - } - } - - if (context.getResultVersion() > 0) { - timeZone = SFTimestamp.convertTimezoneIndexToTimeZone(timeZoneIndex); - } else { - timeZone = TimeZone.getTimeZone("UTC"); - } - Timestamp ts = ArrowResultUtil.createTimestamp(epoch, fraction, timeZone, useSessionTimezone); - Timestamp adjustedTimestamp = ResultUtil.adjustTimestamp(ts); - return adjustedTimestamp; + timeZone = convertFromTimeZoneIndex(timeZoneIndex, context.getResultVersion()); + return getTimestamp( + epoch, + fraction, + timeZoneIndex, + context.getResultVersion(), + useSessionTimezone, + fromToString); } @Override @@ -138,4 +128,32 @@ public short toShort(int rowIndex) throws SFException { throw new SFException( ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.SHORT_STR, ""); } + + public static Timestamp getTimestamp( + long epoch, + int fraction, + int timeZoneIndex, + long resultVersion, + boolean useSessionTimezone, + boolean fromToString) + throws SFException { + if (ArrowResultUtil.isTimestampOverflow(epoch)) { + if (fromToString) { + throw new TimestampOperationNotAvailableException(epoch, fraction); + } else { + return null; + } + } + TimeZone timeZone = convertFromTimeZoneIndex(timeZoneIndex, resultVersion); + Timestamp ts = ArrowResultUtil.createTimestamp(epoch, fraction, timeZone, useSessionTimezone); + return ResultUtil.adjustTimestamp(ts); + } + + private static TimeZone convertFromTimeZoneIndex(int timeZoneIndex, long resultVersion) { + if (resultVersion > 0) { + return SFTimestamp.convertTimezoneIndexToTimeZone(timeZoneIndex); + } else { + return TimeZone.getTimeZone("UTC"); + } + } } diff --git a/src/main/java/net/snowflake/client/core/arrow/TwoFieldStructToTimestampLTZConverter.java b/src/main/java/net/snowflake/client/core/arrow/TwoFieldStructToTimestampLTZConverter.java index 339f3c541..86eeb93b8 100644 --- a/src/main/java/net/snowflake/client/core/arrow/TwoFieldStructToTimestampLTZConverter.java +++ b/src/main/java/net/snowflake/client/core/arrow/TwoFieldStructToTimestampLTZConverter.java @@ -72,20 +72,7 @@ private Timestamp getTimestamp(int index, TimeZone tz, boolean fromToString) thr long epoch = epochs.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH); int fraction = fractions.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH); - if (ArrowResultUtil.isTimestampOverflow(epoch)) { - if (fromToString) { - throw new TimestampOperationNotAvailableException(epoch, fraction); - } else { - return null; - } - } - - Timestamp ts = - ArrowResultUtil.createTimestamp(epoch, fraction, sessionTimeZone, useSessionTimezone); - - Timestamp adjustedTimestamp = ResultUtil.adjustTimestamp(ts); - - return adjustedTimestamp; + return getTimestamp(epoch, fraction, sessionTimeZone, useSessionTimezone, fromToString); } @Override @@ -127,4 +114,23 @@ public boolean toBoolean(int index) throws SFException { ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.BOOLEAN_STR, val); } + + public static Timestamp getTimestamp( + long epoch, + int fraction, + TimeZone sessionTimeZone, + boolean useSessionTimezone, + boolean fromToString) + throws SFException { + if (ArrowResultUtil.isTimestampOverflow(epoch)) { + if (fromToString) { + throw new TimestampOperationNotAvailableException(epoch, fraction); + } else { + return null; + } + } + Timestamp ts = + ArrowResultUtil.createTimestamp(epoch, fraction, sessionTimeZone, useSessionTimezone); + return ResultUtil.adjustTimestamp(ts); + } } diff --git a/src/main/java/net/snowflake/client/core/arrow/TwoFieldStructToTimestampNTZConverter.java b/src/main/java/net/snowflake/client/core/arrow/TwoFieldStructToTimestampNTZConverter.java index bf92fee4f..f4d0d9417 100644 --- a/src/main/java/net/snowflake/client/core/arrow/TwoFieldStructToTimestampNTZConverter.java +++ b/src/main/java/net/snowflake/client/core/arrow/TwoFieldStructToTimestampNTZConverter.java @@ -74,32 +74,15 @@ public Timestamp toTimestamp(int index, TimeZone tz) throws SFException { private Timestamp getTimestamp(int index, TimeZone tz, boolean fromToString) throws SFException { long epoch = epochs.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH); int fraction = fractions.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH); - - if (ArrowResultUtil.isTimestampOverflow(epoch)) { - if (fromToString) { - throw new TimestampOperationNotAvailableException(epoch, fraction); - } else { - return null; - } - } - Timestamp ts; - if (this.treatNTZasUTC || !this.useSessionTimezone) { - ts = ArrowResultUtil.createTimestamp(epoch, fraction, TimeZone.getTimeZone("UTC"), true); - } else { - ts = ArrowResultUtil.createTimestamp(epoch, fraction, sessionTimeZone, false); - } - - // Note: honorClientTZForTimestampNTZ is not enabled for toString method. - // 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. UseSessionTimezone overrides treatNTZasUTC. - if (!fromToString - && ((context.getHonorClientTZForTimestampNTZ() && !this.treatNTZasUTC) - || this.useSessionTimezone)) { - ts = ArrowResultUtil.moveToTimeZone(ts, NTZ, tz); - } - Timestamp adjustedTimestamp = ResultUtil.adjustTimestamp(ts); - return adjustedTimestamp; + return getTimestamp( + epoch, + fraction, + tz, + sessionTimeZone, + treatNTZasUTC, + useSessionTimezone, + context.getHonorClientTZForTimestampNTZ(), + fromToString); } @Override @@ -139,4 +122,39 @@ public boolean toBoolean(int index) throws SFException { ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.BOOLEAN_STR, val); } + + public static Timestamp getTimestamp( + long epoch, + int fraction, + TimeZone tz, + TimeZone sessionTimeZone, + boolean treatNTZasUTC, + boolean useSessionTimezone, + boolean honorClientTZForTimestampNTZ, + boolean fromToString) + throws SFException { + + if (ArrowResultUtil.isTimestampOverflow(epoch)) { + if (fromToString) { + throw new TimestampOperationNotAvailableException(epoch, fraction); + } else { + return null; + } + } + Timestamp ts; + if (treatNTZasUTC || !useSessionTimezone) { + ts = ArrowResultUtil.createTimestamp(epoch, fraction, TimeZone.getTimeZone("UTC"), true); + } else { + ts = ArrowResultUtil.createTimestamp(epoch, fraction, sessionTimeZone, false); + } + + // Note: honorClientTZForTimestampNTZ is not enabled for toString method. + // 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. UseSessionTimezone overrides treatNTZasUTC. + if (!fromToString && (honorClientTZForTimestampNTZ && !treatNTZasUTC) || useSessionTimezone) { + ts = ArrowResultUtil.moveToTimeZone(ts, NTZ, tz); + } + return ResultUtil.adjustTimestamp(ts); + } } diff --git a/src/main/java/net/snowflake/client/core/arrow/TwoFieldStructToTimestampTZConverter.java b/src/main/java/net/snowflake/client/core/arrow/TwoFieldStructToTimestampTZConverter.java index 643480fbb..2068073a7 100644 --- a/src/main/java/net/snowflake/client/core/arrow/TwoFieldStructToTimestampTZConverter.java +++ b/src/main/java/net/snowflake/client/core/arrow/TwoFieldStructToTimestampTZConverter.java @@ -66,17 +66,12 @@ private Timestamp getTimestamp(int index, TimeZone tz) throws SFException { long epoch = epochs.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH); int timeZoneIndex = timeZoneIndices.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH); - Timestamp ts = ArrowResultUtil.toJavaTimestamp(epoch, context.getScale(columnIndex)); - if (context.getResultVersion() > 0) { timeZone = SFTimestamp.convertTimezoneIndexToTimeZone(timeZoneIndex); } else { timeZone = TimeZone.getTimeZone("UTC"); } - - Timestamp adjustedTimestamp = ResultUtil.adjustTimestamp(ts); - - return adjustedTimestamp; + return getTimestamp(epoch, timeZoneIndex, context.getScale(columnIndex)); } @Override @@ -125,4 +120,10 @@ public short toShort(int rowIndex) throws SFException { throw new SFException( ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.SHORT_STR, ""); } + + public static Timestamp getTimestamp(long epoch, int timeZoneIndex, int scale) + throws SFException { + Timestamp ts = ArrowResultUtil.toJavaTimestamp(epoch, scale); + return ResultUtil.adjustTimestamp(ts); + } } diff --git a/src/main/java/net/snowflake/client/core/json/BytesConverter.java b/src/main/java/net/snowflake/client/core/json/BytesConverter.java index 9e4b01ef5..e53771639 100644 --- a/src/main/java/net/snowflake/client/core/json/BytesConverter.java +++ b/src/main/java/net/snowflake/client/core/json/BytesConverter.java @@ -20,6 +20,9 @@ public byte[] getBytes(Object obj, int columnType, int columnSubType, Integer sc if (obj == null) { return null; } + if (obj instanceof byte[]) { + return (byte[]) obj; + } try { // For all types except time/date/timestamp data, convert data into byte array. Different diff --git a/src/main/java/net/snowflake/client/core/json/Converters.java b/src/main/java/net/snowflake/client/core/json/Converters.java index c94361f51..ab8bf60bb 100644 --- a/src/main/java/net/snowflake/client/core/json/Converters.java +++ b/src/main/java/net/snowflake/client/core/json/Converters.java @@ -1,7 +1,5 @@ package net.snowflake.client.core.json; -import static net.snowflake.client.jdbc.SnowflakeUtil.getTimestampFromType; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.sql.Date; @@ -16,6 +14,8 @@ import net.snowflake.client.core.SFBaseSession; import net.snowflake.client.core.SFException; import net.snowflake.client.core.SnowflakeJdbcInternalApi; +import net.snowflake.client.core.SqlInputTimestampUtil; +import net.snowflake.client.core.arrow.StructuredTypeDateTimeConverter; import net.snowflake.client.jdbc.ErrorCode; import net.snowflake.client.jdbc.SnowflakeResultSetSerializableV1; import net.snowflake.client.util.JsonStringToTypeConverter; @@ -29,6 +29,7 @@ public class Converters { private final DateTimeConverter dateTimeConverter; private final BytesConverter bytesConverter; private final StringConverter stringConverter; + private final StructuredTypeDateTimeConverter structuredTypeDateTimeConverter; public Converters( TimeZone sessionTimeZone, @@ -68,6 +69,14 @@ public Converters( resultVersion, session, this); + structuredTypeDateTimeConverter = + new StructuredTypeDateTimeConverter( + sessionTimeZone, + resultVersion, + honorClientTZForTimestampNTZ, + treatNTZAsUTC, + useSessionTimezone, + formatDateWithTimeZone); } @SnowflakeJdbcInternalApi @@ -108,6 +117,10 @@ public StringConverter getStringConverter() { return stringConverter; } + public StructuredTypeDateTimeConverter getStructuredTypeDateTimeConverter() { + return structuredTypeDateTimeConverter; + } + @SnowflakeJdbcInternalApi public JsonStringToTypeConverter integerConverter(int columnType) { return value -> getNumberConverter().getInt(value, columnType); @@ -194,9 +207,16 @@ public JsonStringToTypeConverter timeConverter(SFBaseSession session) { @SnowflakeJdbcInternalApi public JsonStringToTypeConverter timestampConverter( - int columnSubType, int columnType, int scale, SFBaseSession session) { + int columnSubType, + int columnType, + int scale, + SFBaseSession session, + TimeZone tz, + TimeZone sessionTimezone) { return value -> { - Timestamp result = getTimestampFromType(columnSubType, (String) value, session); + Timestamp result = + SqlInputTimestampUtil.getTimestampFromType( + columnSubType, (String) value, session, sessionTimezone, tz); if (result != null) { return result; } diff --git a/src/main/java/net/snowflake/client/core/json/DateTimeConverter.java b/src/main/java/net/snowflake/client/core/json/DateTimeConverter.java index 2622e259a..3203faff1 100644 --- a/src/main/java/net/snowflake/client/core/json/DateTimeConverter.java +++ b/src/main/java/net/snowflake/client/core/json/DateTimeConverter.java @@ -1,3 +1,7 @@ +/* + * Copyright (c) 2012-2024 Snowflake Computing Inc. All rights reserved. + */ + package net.snowflake.client.core.json; import java.sql.Date; diff --git a/src/main/java/net/snowflake/client/jdbc/ArrowResultChunk.java b/src/main/java/net/snowflake/client/jdbc/ArrowResultChunk.java index c4bf33241..c273a8817 100644 --- a/src/main/java/net/snowflake/client/jdbc/ArrowResultChunk.java +++ b/src/main/java/net/snowflake/client/jdbc/ArrowResultChunk.java @@ -29,6 +29,7 @@ import net.snowflake.client.core.arrow.IntToTimeConverter; import net.snowflake.client.core.arrow.SmallIntToFixedConverter; import net.snowflake.client.core.arrow.SmallIntToScaledFixedConverter; +import net.snowflake.client.core.arrow.StructConverter; import net.snowflake.client.core.arrow.ThreeFieldStructToTimestampTZConverter; import net.snowflake.client.core.arrow.TinyIntToFixedConverter; import net.snowflake.client.core.arrow.TinyIntToScaledFixedConverter; @@ -201,11 +202,18 @@ private static List initConverters( case ARRAY: case CHAR: case TEXT: - case OBJECT: case VARIANT: converters.add(new VarCharConverter(vector, i, context)); break; + case OBJECT: + if (vector instanceof StructVector) { + converters.add(new StructConverter((StructVector) vector, i, context)); + } else { + converters.add(new VarCharConverter(vector, i, context)); + } + break; + case BINARY: converters.add(new VarBinaryToBinaryConverter(vector, i, context)); break; diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java index 60918ae64..5f69a1cc5 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java @@ -1405,7 +1405,8 @@ public T[] getArray(int columnIndex, Class type) throws SQLException { OBJECT_MAPPER.convertValue(map, JsonNode.class), session, sfBaseResultSet.getConverters(), - sfBaseResultSet.getMetaData().getColumnMetadata().get(columnIndex - 1).getFields()); + sfBaseResultSet.getMetaData().getColumnMetadata().get(columnIndex - 1).getFields(), + sfBaseResultSet.getSessionTimezone()); instance.readSQL(sqlInput, null); arr[counter++] = (T) instance; } @@ -1427,7 +1428,8 @@ public Map getMap(int columnIndex, Class type) throws SQLExcep jsonNode.get(entry.getKey()), session, sfBaseResultSet.getConverters(), - sfBaseResultSet.getMetaData().getColumnMetadata().get(columnIndex - 1).getFields()); + sfBaseResultSet.getMetaData().getColumnMetadata().get(columnIndex - 1).getFields(), + sfBaseResultSet.getSessionTimezone()); instance.readSQL(sqlInput, null); resultMap.put(entry.getKey(), (T) instance); } diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java index a12b7285a..2e483fe5b 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java @@ -54,6 +54,7 @@ public static SnowflakeType fromString(String name) { } public static JavaDataType getJavaType(SnowflakeType type) { + // TODO structuredType fill for Array and Map: SNOW-1234216, SNOW-1234214 switch (type) { case TEXT: return JavaDataType.JAVA_STRING; diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java index a5128afd9..fc0b5dde4 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java @@ -15,8 +15,8 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; +import java.sql.SQLException; import java.sql.Time; -import java.sql.Timestamp; import java.sql.Types; import java.time.Instant; import java.time.LocalDateTime; @@ -36,12 +36,13 @@ import net.snowflake.client.core.HttpClientSettingsKey; import net.snowflake.client.core.OCSPMode; import net.snowflake.client.core.SFBaseSession; +import net.snowflake.client.core.SFException; import net.snowflake.client.core.SFSessionProperty; import net.snowflake.client.core.SnowflakeJdbcInternalApi; import net.snowflake.client.core.structs.StructureTypeHelper; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; -import net.snowflake.common.core.SnowflakeDateTimeFormat; +import net.snowflake.client.util.ThrowingCallable; import net.snowflake.common.core.SqlState; import net.snowflake.common.util.ClassUtil; import net.snowflake.common.util.FixedViewColumn; @@ -773,39 +774,6 @@ public static Time getTimeInSessionTimezone(Long time, int nanos) { return ts; } - /** - * Helper function to convert system properties to boolean - * - * @param columnSubType column subtype value - * @param value value to convert - * @param session session object - * @return converted Timestamp object - */ - @SnowflakeJdbcInternalApi - public static Timestamp getTimestampFromType( - int columnSubType, String value, SFBaseSession session) { - if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ) { - return getTimestampFromFormat("TIMESTAMP_LTZ_OUTPUT_FORMAT", value, session); - } else if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_NTZ - || columnSubType == Types.TIMESTAMP) { - return getTimestampFromFormat("TIMESTAMP_NTZ_OUTPUT_FORMAT", value, session); - } else if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ) { - return getTimestampFromFormat("TIMESTAMP_TZ_OUTPUT_FORMAT", value, session); - } else { - return null; - } - } - - private static Timestamp getTimestampFromFormat( - String format, String value, SFBaseSession session) { - String rawFormat = (String) session.getCommonParameters().get(format); - if (rawFormat == null || rawFormat.equals("")) { - rawFormat = (String) session.getCommonParameters().get("TIMESTAMP_OUTPUT_FORMAT"); - } - SnowflakeDateTimeFormat formatter = SnowflakeDateTimeFormat.fromSqlFormat(rawFormat); - return formatter.parse(value).getTimestamp(); - } - /** * Helper function to convert system properties to boolean * @@ -822,4 +790,12 @@ public static boolean convertSystemPropertyToBooleanValue( } return defaultValue; } + + public static T mapExceptions(ThrowingCallable action) throws SQLException { + try { + return action.call(); + } catch (SFException e) { + throw new SQLException(e); + } + } } diff --git a/src/main/java/net/snowflake/client/util/ThrowingBiFunction.java b/src/main/java/net/snowflake/client/util/ThrowingBiFunction.java new file mode 100644 index 000000000..93545d798 --- /dev/null +++ b/src/main/java/net/snowflake/client/util/ThrowingBiFunction.java @@ -0,0 +1,9 @@ +package net.snowflake.client.util; + +import net.snowflake.client.core.SnowflakeJdbcInternalApi; + +@SnowflakeJdbcInternalApi +@FunctionalInterface +public interface ThrowingBiFunction { + R apply(A a, B b) throws T; +} diff --git a/src/test/java/net/snowflake/client/AbstractDriverIT.java b/src/test/java/net/snowflake/client/AbstractDriverIT.java index 7f762d48b..b44cc31ef 100644 --- a/src/test/java/net/snowflake/client/AbstractDriverIT.java +++ b/src/test/java/net/snowflake/client/AbstractDriverIT.java @@ -323,7 +323,6 @@ public static Connection getConnection( properties.put("ssl", params.get("ssl")); properties.put("internal", Boolean.TRUE.toString()); // TODO: do we need this? - properties.put("insecureMode", false); // use OCSP for all tests. if (injectSocketTimeout > 0) { diff --git a/src/test/java/net/snowflake/client/core/SqlInputTimestampUtilTest.java b/src/test/java/net/snowflake/client/core/SqlInputTimestampUtilTest.java new file mode 100644 index 000000000..7d2b22d33 --- /dev/null +++ b/src/test/java/net/snowflake/client/core/SqlInputTimestampUtilTest.java @@ -0,0 +1,54 @@ +package net.snowflake.client.core; + +import static org.junit.Assert.assertEquals; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; +import net.snowflake.client.jdbc.SnowflakeUtil; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; + +@Ignore +public class SqlInputTimestampUtilTest { + + private static final String TIMESTAMP_IN_FORMAT_1 = "2021-12-22 09:43:44.000 +0100"; + private static final String TIMESTAMP_IN_FORMAT_2 = "Wed, 22 Dec 2021 09:43:44 +0100"; + private static final Map CONNECTION_PARAMS = new HashMap<>(); + private static final Timestamp EXPECTED_TIMESTAMP = + Timestamp.valueOf(LocalDateTime.of(2021, 12, 22, 9, 43, 44)); + + private static SFBaseSession mockSession; + + @BeforeClass + public static void setup() { + CONNECTION_PARAMS.put("TIMESTAMP_OUTPUT_FORMAT", "YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM"); + CONNECTION_PARAMS.put("TIMESTAMP_TZ_OUTPUT_FORMAT", "DY, DD MON YYYY HH24:MI:SS TZHTZM"); + mockSession = Mockito.mock(SFBaseSession.class); + Mockito.when(mockSession.getCommonParameters()).thenReturn(CONNECTION_PARAMS); + } + + @Test + public void shouldGetTimestampForDifferentType() { + // when + Timestamp resultLtz = + getFromType(SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ, TIMESTAMP_IN_FORMAT_1, null); + Timestamp resultTz = + getFromType(SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ, TIMESTAMP_IN_FORMAT_2, null); + Timestamp resultNtz = + getFromType(SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_NTZ, TIMESTAMP_IN_FORMAT_1, null); + + assertEquals(EXPECTED_TIMESTAMP, resultLtz); + assertEquals(EXPECTED_TIMESTAMP, resultTz); + assertEquals(EXPECTED_TIMESTAMP, resultNtz); + } + + private Timestamp getFromType(int type, String value, TimeZone explicitTimezone) { + return SqlInputTimestampUtil.getTimestampFromType( + type, value, mockSession, TimeZone.getTimeZone("GMT"), explicitTimezone); + } +} diff --git a/src/test/java/net/snowflake/client/jdbc/ArrowResultSetStructuredTypesLatestIT.java b/src/test/java/net/snowflake/client/jdbc/ArrowResultSetStructuredTypesLatestIT.java index 291bb9e34..34611a524 100644 --- a/src/test/java/net/snowflake/client/jdbc/ArrowResultSetStructuredTypesLatestIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ArrowResultSetStructuredTypesLatestIT.java @@ -5,6 +5,6 @@ public class ArrowResultSetStructuredTypesLatestIT extends ResultSetStructuredTypesLatestIT { public ArrowResultSetStructuredTypesLatestIT() { - super("ARROW"); + super(ResultSetFormatType.ARROW_WITH_JSON_STRUCTURED_TYPES); } } diff --git a/src/test/java/net/snowflake/client/jdbc/NativeArrowResultSetStructuredTypesLatestIT.java b/src/test/java/net/snowflake/client/jdbc/NativeArrowResultSetStructuredTypesLatestIT.java new file mode 100644 index 000000000..7772c6b03 --- /dev/null +++ b/src/test/java/net/snowflake/client/jdbc/NativeArrowResultSetStructuredTypesLatestIT.java @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2012-2024 Snowflake Computing Inc. All right reserved. + */ +package net.snowflake.client.jdbc; + +public class NativeArrowResultSetStructuredTypesLatestIT extends ResultSetStructuredTypesLatestIT { + public NativeArrowResultSetStructuredTypesLatestIT() { + super(ResultSetFormatType.NATIVE_ARROW); + } +} diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetStructuredTypesLatestIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetStructuredTypesLatestIT.java index 0b04a8737..f54887d2d 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetStructuredTypesLatestIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetStructuredTypesLatestIT.java @@ -28,19 +28,20 @@ import net.snowflake.client.category.TestCategoryStructuredType; import net.snowflake.client.core.structs.SnowflakeObjectTypeFactories; import net.snowflake.client.core.structs.StructureTypeHelper; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; @Category(TestCategoryStructuredType.class) public class ResultSetStructuredTypesLatestIT extends BaseJDBCTest { - private final String queryResultFormat; + private final ResultSetFormatType queryResultFormat; public ResultSetStructuredTypesLatestIT() { - this("JSON"); + this(ResultSetFormatType.JSON); } - protected ResultSetStructuredTypesLatestIT(String queryResultFormat) { + protected ResultSetStructuredTypesLatestIT(ResultSetFormatType queryResultFormat) { this.queryResultFormat = queryResultFormat; } @@ -49,7 +50,14 @@ public Connection init() throws SQLException { try (Statement stmt = conn.createStatement()) { stmt.execute("alter session set ENABLE_STRUCTURED_TYPES_IN_CLIENT_RESPONSE = true"); stmt.execute("alter session set IGNORE_CLIENT_VESRION_IN_STRUCTURED_TYPES_RESPONSE = true"); - stmt.execute("alter session set jdbc_query_result_format = '" + queryResultFormat + "'"); + stmt.execute( + "alter session set jdbc_query_result_format = '" + + queryResultFormat.sessionParameterTypeValue + + "'"); + if (queryResultFormat == ResultSetFormatType.NATIVE_ARROW) { + stmt.execute("alter session set ENABLE_STRUCTURED_TYPES_NATIVE_ARROW_FORMAT = true"); + stmt.execute("alter session set FORCE_ENABLE_STRUCTURED_TYPES_NATIVE_ARROW_FORMAT = true"); + } } return conn; } @@ -154,11 +162,11 @@ private void testMapAllTypes(boolean registerFactory) throws SQLException { assertEquals( Timestamp.valueOf(LocalDateTime.of(2021, 12, 22, 9, 43, 44)), object.getTimestampLtz()); assertEquals( - Timestamp.valueOf(LocalDateTime.of(2021, 12, 23, 10, 44, 44)), - object.getTimestampNtz()); + Timestamp.valueOf(LocalDateTime.of(2021, 12, 23, 9, 44, 44)), object.getTimestampNtz()); assertEquals( Timestamp.valueOf(LocalDateTime.of(2021, 12, 24, 2, 45, 45)), object.getTimestampTz()); - assertEquals(Date.valueOf(LocalDate.of(2023, 12, 24)), object.getDate()); + assertEquals( + Date.valueOf(LocalDate.of(2023, 12, 24)).toString(), object.getDate().toString()); assertEquals(Time.valueOf(LocalTime.of(12, 34, 56)), object.getTime()); assertArrayEquals(new byte[] {'a', 'b', 'c'}, object.getBinary()); assertTrue(object.getBool()); @@ -170,6 +178,7 @@ private void testMapAllTypes(boolean registerFactory) throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testMapJsonToMap() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT OBJECT_CONSTRUCT('string','a','string2',1)", (resultSet) -> { @@ -182,6 +191,7 @@ public void testMapJsonToMap() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testReturnAsArrayOfSqlData() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); SnowflakeObjectTypeFactories.register(SimpleClass.class, SimpleClass::new); withFirstRow( "SELECT ARRAY_CONSTRUCT({'string':'one'}, {'string':'two'}, {'string':'three'})::ARRAY(OBJECT(string VARCHAR))", @@ -197,6 +207,7 @@ public void testReturnAsArrayOfSqlData() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testReturnAsMap() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); SnowflakeObjectTypeFactories.register(SimpleClass.class, SimpleClass::new); withFirstRow( "select {'x':{'string':'one'},'y':{'string':'two'},'z':{'string':'three'}}::MAP(VARCHAR, OBJECT(string VARCHAR));", @@ -212,6 +223,7 @@ public void testReturnAsMap() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testReturnAsList() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); SnowflakeObjectTypeFactories.register(SimpleClass.class, SimpleClass::new); withFirstRow( "select [{'string':'one'},{'string': 'two'}]::ARRAY(OBJECT(string varchar))", @@ -227,6 +239,7 @@ public void testReturnAsList() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testMapStructsFromChunks() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "select {'string':'a'}::OBJECT(string VARCHAR) FROM TABLE(GENERATOR(ROWCOUNT=>30000))", (resultSet) -> { @@ -240,6 +253,7 @@ public void testMapStructsFromChunks() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testMapIntegerArray() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT ARRAY_CONSTRUCT(10, 20, 30)::ARRAY(INTEGER)", (resultSet) -> { @@ -253,6 +267,7 @@ public void testMapIntegerArray() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testMapFixedToLongArray() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT ARRAY_CONSTRUCT(10, 20, 30)::ARRAY(SMALLINT)", (resultSet) -> { @@ -266,6 +281,7 @@ public void testMapFixedToLongArray() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testMapDecimalArray() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); // when: jdbc_treat_decimal_as_int=true scale=0 try (Connection connection = init(); Statement statement = connection.createStatement(); @@ -310,6 +326,7 @@ public void testMapDecimalArray() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testMapVarcharArray() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT 'text', ARRAY_CONSTRUCT('10', '20','30')::ARRAY(VARCHAR)", (resultSet) -> { @@ -324,6 +341,7 @@ public void testMapVarcharArray() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testMapDatesArray() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT ARRAY_CONSTRUCT(to_date('2023-12-24', 'YYYY-MM-DD'), to_date('2023-12-25', 'YYYY-MM-DD'))::ARRAY(DATE)", (resultSet) -> { @@ -336,6 +354,7 @@ public void testMapDatesArray() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testMapTimeArray() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT ARRAY_CONSTRUCT(to_time('15:39:20.123'), to_time('15:39:20.123'))::ARRAY(TIME)", (resultSet) -> { @@ -348,6 +367,7 @@ public void testMapTimeArray() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testMapTimestampArray() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT ARRAY_CONSTRUCT(TO_TIMESTAMP_NTZ('2021-12-23 09:44:44'), TO_TIMESTAMP_NTZ('2021-12-24 09:55:55'))::ARRAY(TIMESTAMP)", (resultSet) -> { @@ -362,6 +382,7 @@ public void testMapTimestampArray() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testMapBooleanArray() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT ARRAY_CONSTRUCT(true,false)::ARRAY(BOOLEAN)", (resultSet) -> { @@ -374,6 +395,7 @@ public void testMapBooleanArray() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testMapBinaryArray() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT ARRAY_CONSTRUCT(TO_BINARY('616263', 'HEX'),TO_BINARY('616263', 'HEX'))::ARRAY(BINARY)", (resultSet) -> { @@ -386,6 +408,7 @@ public void testMapBinaryArray() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testMapArrayOfStructToMap() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT ARRAY_CONSTRUCT({'x': 'abc', 'y': 1}, {'x': 'def', 'y': 2} )::ARRAY(OBJECT(x VARCHAR, y INTEGER))", (resultSet) -> { @@ -398,6 +421,7 @@ public void testMapArrayOfStructToMap() throws SQLException { @Test @ConditionalIgnoreRule.ConditionalIgnore(condition = RunningOnGithubAction.class) public void testMapArrayOfArrays() throws SQLException { + Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT ARRAY_CONSTRUCT(ARRAY_CONSTRUCT({'x': 'abc', 'y': 1}, {'x': 'def', 'y': 2}) )::ARRAY(ARRAY(OBJECT(x VARCHAR, y INTEGER)))", (resultSet) -> { @@ -466,4 +490,16 @@ private void withStructureTypeTemporaryDisabled(ThrowingRunnable action) throws } } } + + enum ResultSetFormatType { + JSON("JSON"), + ARROW_WITH_JSON_STRUCTURED_TYPES("ARROW"), + NATIVE_ARROW("ARROW"); + + public final String sessionParameterTypeValue; + + ResultSetFormatType(String sessionParameterTypeValue) { + this.sessionParameterTypeValue = sessionParameterTypeValue; + } + } }