diff --git a/src/main/java/net/snowflake/client/core/ArrowSqlInput.java b/src/main/java/net/snowflake/client/core/ArrowSqlInput.java index 2ea95de0b..f600ccff7 100644 --- a/src/main/java/net/snowflake/client/core/ArrowSqlInput.java +++ b/src/main/java/net/snowflake/client/core/ArrowSqlInput.java @@ -187,11 +187,7 @@ public Timestamp readTimestamp(TimeZone tz) throws SQLException { converters .getStructuredTypeDateTimeConverter() .getTimestamp( - (JsonStringHashMap) value, - columnType, - columnSubType, - tz, - scale)); + (Map) value, columnType, columnSubType, tz, scale)); }); } diff --git a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java index 58aa3a709..5bb67919e 100644 --- a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java @@ -16,11 +16,15 @@ import java.sql.Array; import java.sql.Date; import java.sql.SQLException; +import java.sql.SQLInput; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; +import java.util.List; import java.util.Map; import java.util.TimeZone; +import java.util.stream.Stream; +import net.snowflake.client.core.arrow.ArrayConverter; import net.snowflake.client.core.arrow.ArrowVectorConverter; import net.snowflake.client.core.arrow.StructConverter; import net.snowflake.client.core.arrow.VarCharConverter; @@ -29,6 +33,8 @@ import net.snowflake.client.jdbc.ArrowResultChunk; import net.snowflake.client.jdbc.ArrowResultChunk.ArrowChunkIterator; import net.snowflake.client.jdbc.ErrorCode; +import net.snowflake.client.jdbc.FieldMetadata; +import net.snowflake.client.jdbc.SnowflakeColumnMetadata; import net.snowflake.client.jdbc.SnowflakeResultSetSerializableV1; import net.snowflake.client.jdbc.SnowflakeSQLException; import net.snowflake.client.jdbc.SnowflakeSQLLoggedException; @@ -38,6 +44,7 @@ import net.snowflake.client.jdbc.telemetry.TelemetryUtil; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; +import net.snowflake.client.util.Converter; import net.snowflake.common.core.SFBinaryFormat; import net.snowflake.common.core.SnowflakeDateTimeFormat; import net.snowflake.common.core.SqlState; @@ -199,7 +206,7 @@ public SFArrowResultSet( this.timestampTZFormatter = resultSetSerializable.getTimestampTZFormatter(); this.dateFormatter = resultSetSerializable.getDateFormatter(); this.timeFormatter = resultSetSerializable.getTimeFormatter(); - this.sessionTimezone = resultSetSerializable.getTimeZone(); + this.sessionTimeZone = resultSetSerializable.getTimeZone(); this.binaryFormatter = resultSetSerializable.getBinaryFormatter(); this.resultSetMetaData = resultSetSerializable.getSFResultSetMetaData(); this.treatNTZAsUTC = resultSetSerializable.getTreatNTZAsUTC(); @@ -364,18 +371,45 @@ public Converters getConverters() { } @Override + @SnowflakeJdbcInternalApi + public SQLInput createSqlInputForColumn( + Object input, Class parentObjectClass, int columnIndex, SFBaseSession session) { + if (parentObjectClass.equals(JsonSqlInput.class)) { + return createJsonSqlInputForColumn(input, columnIndex, session); + } else { + return new ArrowSqlInput( + (Map) input, + session, + converters, + resultSetMetaData.getColumnMetadata().get(columnIndex - 1).getFields()); + } + } + + @Override + @SnowflakeJdbcInternalApi public Date convertToDate(Object object, TimeZone tz) throws SFException { + if (object instanceof String) { + return convertStringToDate((String) object, tz); + } return converters.getStructuredTypeDateTimeConverter().getDate((int) object, tz); } @Override + @SnowflakeJdbcInternalApi public Time convertToTime(Object object, int scale) throws SFException { + if (object instanceof String) { + return convertStringToTime((String) object, scale); + } return converters.getStructuredTypeDateTimeConverter().getTime((long) object, scale); } @Override + @SnowflakeJdbcInternalApi public Timestamp convertToTimestamp( Object object, int columnType, int columnSubType, TimeZone tz, int scale) throws SFException { + if (object instanceof String) { + return convertStringToTimestamp((String) object, columnType, columnSubType, tz, scale); + } return converters .getStructuredTypeDateTimeConverter() .getTimestamp( @@ -497,7 +531,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(sessionTimezone); + converter.setSessionTimeZone(sessionTimeZone); converter.setUseSessionTimezone(useSessionTimezone); return converter.toDate(index, tz, resultSetSerializable.getFormatDateWithTimeZone()); } @@ -507,7 +541,7 @@ public Time getTime(int columnIndex) throws SFException { ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1); int index = currentChunkIterator.getCurrentRowInRecordBatch(); wasNull = converter.isNull(index); - converter.setSessionTimeZone(sessionTimezone); + converter.setSessionTimeZone(sessionTimeZone); converter.setUseSessionTimezone(useSessionTimezone); return converter.toTime(index); } @@ -516,7 +550,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(sessionTimezone); + converter.setSessionTimeZone(sessionTimeZone); converter.setUseSessionTimezone(useSessionTimezone); wasNull = converter.isNull(index); return converter.toTimestamp(index, tz); @@ -529,7 +563,7 @@ public Object getObject(int columnIndex) throws SFException { wasNull = converter.isNull(index); converter.setTreatNTZAsUTC(treatNTZAsUTC); converter.setUseSessionTimezone(useSessionTimezone); - converter.setSessionTimeZone(sessionTimezone); + converter.setSessionTimeZone(sessionTimeZone); Object obj = converter.toObject(index); int type = resultSetMetaData.getColumnType(columnIndex); if (type == Types.STRUCT && StructureTypeHelper.isStructureTypeEnabled()) { @@ -550,7 +584,7 @@ private Object createJsonSqlInput(int columnIndex, Object obj) throws SFExceptio session, converters, resultSetMetaData.getColumnMetadata().get(columnIndex - 1).getFields(), - sessionTimezone); + sessionTimeZone); } catch (JsonProcessingException e) { throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); } @@ -566,8 +600,126 @@ private Object createArrowSqlInput(int columnIndex, Map input) { @Override public Array getArray(int columnIndex) throws SFException { - // TODO: handleArray SNOW-969794 - throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, "data type ARRAY"); + ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1); + int index = currentChunkIterator.getCurrentRowInRecordBatch(); + wasNull = converter.isNull(index); + Object obj = converter.toObject(index); + if (converter instanceof VarCharConverter) { + return getJsonArray((String) obj, columnIndex); + } else if (converter instanceof ArrayConverter) { + return getArrowArray((List) obj, columnIndex); + } else { + throw new SFException(ErrorCode.INVALID_STRUCT_DATA); + } + } + + private SfSqlArray getArrowArray(List elements, int columnIndex) throws SFException { + try { + SnowflakeColumnMetadata arrayMetadata = + resultSetMetaData.getColumnMetadata().get(columnIndex - 1); + FieldMetadata fieldMetadata = arrayMetadata.getFields().get(0); + + int columnSubType = fieldMetadata.getType(); + int columnType = ColumnTypeHelper.getColumnType(columnSubType, session); + int scale = fieldMetadata.getScale(); + + switch (columnSubType) { + case Types.INTEGER: + return new SfSqlArray( + columnSubType, + mapAndConvert(elements, converters.integerConverter(columnType)) + .toArray(Integer[]::new)); + case Types.SMALLINT: + return new SfSqlArray( + columnSubType, + mapAndConvert(elements, converters.smallIntConverter(columnType)) + .toArray(Short[]::new)); + case Types.TINYINT: + return new SfSqlArray( + columnSubType, + mapAndConvert(elements, converters.tinyIntConverter(columnType)) + .toArray(Byte[]::new)); + case Types.BIGINT: + return new SfSqlArray( + columnSubType, + mapAndConvert(elements, converters.bigIntConverter(columnType)).toArray(Long[]::new)); + case Types.DECIMAL: + case Types.NUMERIC: + return new SfSqlArray( + columnSubType, + mapAndConvert(elements, converters.bigDecimalConverter(columnType)) + .toArray(BigDecimal[]::new)); + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGNVARCHAR: + return new SfSqlArray( + columnSubType, + mapAndConvert(elements, converters.varcharConverter(columnType, columnSubType, scale)) + .toArray(String[]::new)); + case Types.BINARY: + return new SfSqlArray( + columnSubType, + mapAndConvert(elements, converters.bytesConverter(columnType, scale)) + .toArray(Byte[][]::new)); + case Types.FLOAT: + case Types.REAL: + return new SfSqlArray( + columnSubType, + mapAndConvert(elements, converters.floatConverter(columnType)).toArray(Float[]::new)); + case Types.DOUBLE: + return new SfSqlArray( + columnSubType, + mapAndConvert(elements, converters.doubleConverter(columnType)) + .toArray(Double[]::new)); + case Types.DATE: + return new SfSqlArray( + columnSubType, + mapAndConvert(elements, converters.dateFromIntConverter(sessionTimeZone)) + .toArray(Date[]::new)); + case Types.TIME: + return new SfSqlArray( + columnSubType, + mapAndConvert(elements, converters.timeFromIntConverter(scale)).toArray(Time[]::new)); + case Types.TIMESTAMP: + return new SfSqlArray( + columnSubType, + mapAndConvert( + elements, + converters.timestampFromStructConverter( + columnType, columnSubType, sessionTimeZone, scale)) + .toArray(Timestamp[]::new)); + case Types.BOOLEAN: + return new SfSqlArray( + columnSubType, + mapAndConvert(elements, converters.booleanConverter(columnType)) + .toArray(Boolean[]::new)); + case Types.STRUCT: + return new SfSqlArray(columnSubType, mapAndConvert(elements, e -> e).toArray(Map[]::new)); + case Types.ARRAY: + return new SfSqlArray( + columnSubType, + mapAndConvert(elements, e -> ((List) e).stream().toArray(Map[]::new)) + .toArray(Map[][]::new)); + default: + throw new SFException( + ErrorCode.FEATURE_UNSUPPORTED, + "Can't construct array for data type: " + columnSubType); + } + } catch (RuntimeException e) { + throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); + } + } + + private Stream mapAndConvert(List elements, Converter converter) { + return elements.stream() + .map( + obj -> { + try { + return converter.convert(obj); + } catch (SFException e) { + throw new RuntimeException(e); + } + }); } @Override @@ -575,7 +727,7 @@ public BigDecimal getBigDecimal(int columnIndex) throws SFException { ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1); int index = currentChunkIterator.getCurrentRowInRecordBatch(); wasNull = converter.isNull(index); - converter.setSessionTimeZone(sessionTimezone); + converter.setSessionTimeZone(sessionTimeZone); converter.setUseSessionTimezone(useSessionTimezone); return converter.toBigDecimal(index); } @@ -715,7 +867,7 @@ public int getScale(int columnIndex) { @Override public TimeZone getTimeZone() { - return sessionTimezone; + 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 a68bf82b6..24756bbc7 100644 --- a/src/main/java/net/snowflake/client/core/SFBaseResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFBaseResultSet.java @@ -4,30 +4,48 @@ package net.snowflake.client.core; +import static net.snowflake.client.jdbc.SnowflakeUtil.getJsonNodeStringValue; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; import java.math.BigDecimal; import java.sql.Array; import java.sql.Date; import java.sql.SQLException; +import java.sql.SQLInput; import java.sql.Time; import java.sql.Timestamp; +import java.sql.Types; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.TimeZone; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import net.snowflake.client.core.json.Converters; import net.snowflake.client.jdbc.ErrorCode; +import net.snowflake.client.jdbc.FieldMetadata; +import net.snowflake.client.jdbc.SnowflakeColumnMetadata; import net.snowflake.client.jdbc.SnowflakeResultSetSerializable; import net.snowflake.client.jdbc.SnowflakeResultSetSerializableV1; import net.snowflake.client.jdbc.SnowflakeSQLException; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; +import net.snowflake.client.util.Converter; import net.snowflake.common.core.SFBinaryFormat; import net.snowflake.common.core.SnowflakeDateTimeFormat; /** Base class for query result set and metadata result set */ public abstract class SFBaseResultSet { private static final SFLogger logger = SFLoggerFactory.getLogger(SFBaseResultSet.class); + protected static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper(); boolean wasNull = false; @@ -64,7 +82,7 @@ public abstract class SFBaseResultSet { // result set protected SnowflakeResultSetSerializableV1 resultSetSerializable; - protected TimeZone sessionTimezone; + protected TimeZone sessionTimeZone; public abstract boolean isLast(); @@ -96,7 +114,9 @@ public abstract class SFBaseResultSet { public abstract Object getObject(int columnIndex) throws SFException; - public abstract Array getArray(int columnIndex) throws SFException; + public Array getArray(int columnIndex) throws SFException { + throw new UnsupportedOperationException(); + } public abstract BigDecimal getBigDecimal(int columnIndex) throws SFException; @@ -143,7 +163,7 @@ public SFResultSetMetaData getMetaData() { } public TimeZone getSessionTimezone() { - return sessionTimezone; + return sessionTimeZone; } public int getRow() throws SQLException { @@ -215,12 +235,207 @@ public TimeZone getSessionTimeZone() { } @SnowflakeJdbcInternalApi - public abstract Date convertToDate(Object object, TimeZone tz) throws SFException; + public SQLInput createSqlInputForColumn( + Object input, Class parentObjectClass, int columnIndex, SFBaseSession session) { + throw new UnsupportedOperationException(); + } + + @SnowflakeJdbcInternalApi + public Date convertToDate(Object object, TimeZone tz) throws SFException { + throw new UnsupportedOperationException(); + } @SnowflakeJdbcInternalApi - public abstract Time convertToTime(Object object, int scale) throws SFException; + public Time convertToTime(Object object, int scale) throws SFException { + throw new UnsupportedOperationException(); + } @SnowflakeJdbcInternalApi - public abstract Timestamp convertToTimestamp( - Object object, int columnType, int columnSubType, TimeZone tz, int scale) throws SFException; + public Timestamp convertToTimestamp( + Object object, int columnType, int columnSubType, TimeZone tz, int scale) throws SFException { + throw new UnsupportedOperationException(); + } + + @SnowflakeJdbcInternalApi + protected SQLInput createJsonSqlInputForColumn( + Object input, int columnIndex, SFBaseSession session) { + JsonNode inputNode; + if (input instanceof JsonNode) { + inputNode = (JsonNode) input; + } else { + inputNode = OBJECT_MAPPER.convertValue(input, JsonNode.class); + } + return new JsonSqlInput( + inputNode, + session, + getConverters(), + resultSetMetaData.getColumnMetadata().get(columnIndex - 1).getFields(), + sessionTimeZone); + } + + @SnowflakeJdbcInternalApi + protected SfSqlArray getJsonArray(String obj, int columnIndex) throws SFException { + try { + SnowflakeColumnMetadata arrayMetadata = + resultSetMetaData.getColumnMetadata().get(columnIndex - 1); + FieldMetadata fieldMetadata = arrayMetadata.getFields().get(0); + + int columnSubType = fieldMetadata.getType(); + int columnType = ColumnTypeHelper.getColumnType(columnSubType, session); + int scale = fieldMetadata.getScale(); + + ArrayNode arrayNode = (ArrayNode) OBJECT_MAPPER.readTree(obj); + Iterator nodeElements = arrayNode.elements(); + + switch (columnSubType) { + case Types.INTEGER: + return new SfSqlArray( + columnSubType, + getStream(nodeElements, getConverters().integerConverter(columnType)) + .toArray(Integer[]::new)); + case Types.SMALLINT: + return new SfSqlArray( + columnSubType, + getStream(nodeElements, getConverters().smallIntConverter(columnType)) + .toArray(Short[]::new)); + case Types.TINYINT: + return new SfSqlArray( + columnSubType, + getStream(nodeElements, getConverters().tinyIntConverter(columnType)) + .toArray(Byte[]::new)); + case Types.BIGINT: + return new SfSqlArray( + columnSubType, + getStream(nodeElements, getConverters().bigIntConverter(columnType)) + .toArray(Long[]::new)); + case Types.DECIMAL: + case Types.NUMERIC: + return new SfSqlArray( + columnSubType, + convertToFixedArray( + getStream(nodeElements, getConverters().bigDecimalConverter(columnType)))); + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGNVARCHAR: + return new SfSqlArray( + columnSubType, + getStream( + nodeElements, + getConverters().varcharConverter(columnType, columnSubType, scale)) + .toArray(String[]::new)); + case Types.BINARY: + return new SfSqlArray( + columnSubType, + getStream(nodeElements, getConverters().bytesConverter(columnType, scale)) + .toArray(Byte[][]::new)); + case Types.FLOAT: + case Types.REAL: + return new SfSqlArray( + columnSubType, + getStream(nodeElements, getConverters().floatConverter(columnType)) + .toArray(Float[]::new)); + case Types.DOUBLE: + return new SfSqlArray( + columnSubType, + getStream(nodeElements, getConverters().doubleConverter(columnType)) + .toArray(Double[]::new)); + case Types.DATE: + return new SfSqlArray( + columnSubType, + getStream(nodeElements, getConverters().dateStringConverter(session)) + .toArray(Date[]::new)); + case Types.TIME: + return new SfSqlArray( + columnSubType, + getStream(nodeElements, getConverters().timeFromStringConverter(session)) + .toArray(Time[]::new)); + case Types.TIMESTAMP: + return new SfSqlArray( + columnSubType, + getStream( + nodeElements, + getConverters() + .timestampFromStringConverter( + columnSubType, columnType, scale, session, null, sessionTimeZone)) + .toArray(Timestamp[]::new)); + case Types.BOOLEAN: + return new SfSqlArray( + columnSubType, + getStream(nodeElements, getConverters().booleanConverter(columnType)) + .toArray(Boolean[]::new)); + case Types.STRUCT: + return new SfSqlArray( + columnSubType, + getStream(nodeElements, getConverters().structConverter(OBJECT_MAPPER)) + .toArray(Map[]::new)); + case Types.ARRAY: + return new SfSqlArray( + columnSubType, + getStream(nodeElements, getConverters().arrayConverter(OBJECT_MAPPER)) + .toArray(Map[][]::new)); + default: + throw new SFException( + ErrorCode.FEATURE_UNSUPPORTED, + "Can't construct array for data type: " + columnSubType); + } + } catch (JsonProcessingException e) { + throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); + } + } + + @SnowflakeJdbcInternalApi + protected Date convertStringToDate(String object, TimeZone tz) throws SFException { + return (Date) getConverters().dateStringConverter(session).convert(object); + } + + @SnowflakeJdbcInternalApi + protected Time convertStringToTime(String object, int scale) throws SFException { + return (Time) getConverters().timeFromStringConverter(session).convert(object); + } + + @SnowflakeJdbcInternalApi + protected Timestamp convertStringToTimestamp( + String object, int columnType, int columnSubType, TimeZone tz, int scale) throws SFException { + return (Timestamp) + getConverters() + .timestampFromStringConverter(columnSubType, columnType, scale, session, null, tz) + .convert(object); + } + + private Stream getStream(Iterator nodeElements, Converter converter) { + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize(nodeElements, Spliterator.ORDERED), false) + .map( + elem -> { + try { + return convert(converter, (JsonNode) elem); + } catch (SFException e) { + throw new RuntimeException(e); + } + }); + } + + private static Object convert(Converter converter, JsonNode node) throws SFException { + String nodeValue = getJsonNodeStringValue(node); + return converter.convert(nodeValue); + } + + private Object[] convertToFixedArray(Stream inputStream) { + AtomicInteger bigDecimalCount = new AtomicInteger(); + Object[] elements = + inputStream + .peek( + elem -> { + if (elem instanceof BigDecimal) { + bigDecimalCount.incrementAndGet(); + } + }) + .toArray( + size -> { + boolean shouldReturnAsBigDecimal = bigDecimalCount.get() > 0; + Class returnedClass = shouldReturnAsBigDecimal ? BigDecimal.class : Long.class; + return java.lang.reflect.Array.newInstance(returnedClass, size); + }); + return elements; + } } diff --git a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java index 93946a0a3..181492294 100644 --- a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java @@ -4,41 +4,26 @@ package net.snowflake.client.core; -import static net.snowflake.client.jdbc.SnowflakeUtil.getJsonNodeStringValue; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; import java.math.BigDecimal; import java.sql.Array; import java.sql.Date; +import java.sql.SQLInput; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; -import java.util.Iterator; -import java.util.Map; -import java.util.Spliterator; -import java.util.Spliterators; import java.util.TimeZone; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; import net.snowflake.client.core.json.Converters; import net.snowflake.client.core.structs.StructureTypeHelper; import net.snowflake.client.jdbc.ErrorCode; -import net.snowflake.client.jdbc.FieldMetadata; -import net.snowflake.client.jdbc.SnowflakeColumnMetadata; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; -import net.snowflake.client.util.JsonStringToTypeConverter; /** 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); - private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper(); - protected final TimeZone sessionTimeZone; protected final Converters converters; protected SFJsonResultSet(TimeZone sessionTimeZone, Converters converters) { @@ -130,7 +115,7 @@ private Object getBigInt(int columnIndex, Object obj) throws SFException { @Override public Array getArray(int columnIndex) throws SFException { Object obj = getObjectInternal(columnIndex); - return getArrayInternal((String) obj, columnIndex); + return getJsonArray((String) obj, columnIndex); } @Override @@ -261,6 +246,32 @@ public Date getDate(int columnIndex, TimeZone tz) throws SFException { return converters.getDateTimeConverter().getDate(obj, columnType, columnSubType, tz, scale); } + @Override + @SnowflakeJdbcInternalApi + public SQLInput createSqlInputForColumn( + Object input, Class parentObjectClass, int columnIndex, SFBaseSession session) { + return createJsonSqlInputForColumn(input, columnIndex, session); + } + + @Override + @SnowflakeJdbcInternalApi + public Date convertToDate(Object object, TimeZone tz) throws SFException { + return convertStringToDate((String) object, tz); + } + + @Override + @SnowflakeJdbcInternalApi + public Time convertToTime(Object object, int scale) throws SFException { + return convertStringToTime((String) object, scale); + } + + @Override + @SnowflakeJdbcInternalApi + public Timestamp convertToTimestamp( + Object object, int columnType, int columnSubType, TimeZone tz, int scale) throws SFException { + return convertStringToTimestamp((String) object, columnType, columnSubType, tz, scale); + } + private Timestamp getTimestamp(int columnIndex) throws SFException { return getTimestamp(columnIndex, TimeZone.getDefault()); } @@ -284,164 +295,4 @@ private Object getSqlInput(String input, int columnIndex) throws SFException { throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); } } - - private SfSqlArray getArrayInternal(String obj, int columnIndex) throws SFException { - try { - SnowflakeColumnMetadata arrayMetadata = - resultSetMetaData.getColumnMetadata().get(columnIndex - 1); - FieldMetadata fieldMetadata = arrayMetadata.getFields().get(0); - - int columnSubType = fieldMetadata.getType(); - int columnType = ColumnTypeHelper.getColumnType(columnSubType, session); - int scale = fieldMetadata.getScale(); - - ArrayNode arrayNode = (ArrayNode) OBJECT_MAPPER.readTree(obj); - Iterator nodeElements = arrayNode.elements(); - - switch (columnSubType) { - case Types.INTEGER: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.integerConverter(columnType)) - .toArray(Integer[]::new)); - case Types.SMALLINT: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.smallIntConverter(columnType)) - .toArray(Short[]::new)); - case Types.TINYINT: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.tinyIntConverter(columnType)) - .toArray(Byte[]::new)); - case Types.BIGINT: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.bigIntConverter(columnType)).toArray(Long[]::new)); - case Types.DECIMAL: - case Types.NUMERIC: - return new SfSqlArray( - columnSubType, - convertToFixedArray(nodeElements, converters.bigDecimalConverter(columnType))); - case Types.CHAR: - case Types.VARCHAR: - case Types.LONGNVARCHAR: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.varcharConverter(columnType, columnSubType, scale)) - .toArray(String[]::new)); - case Types.BINARY: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.bytesConverter(columnType, scale)) - .toArray(Byte[][]::new)); - case Types.FLOAT: - case Types.REAL: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.floatConverter(columnType)).toArray(Float[]::new)); - case Types.DOUBLE: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.doubleConverter(columnType)) - .toArray(Double[]::new)); - case Types.DATE: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.dateConverter(session)).toArray(Date[]::new)); - case Types.TIME: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.timeConverter(session)).toArray(Time[]::new)); - case Types.TIMESTAMP: - return new SfSqlArray( - columnSubType, - getStream( - nodeElements, - converters.timestampConverter( - columnSubType, columnType, scale, session, null, sessionTimezone)) - .toArray(Timestamp[]::new)); - case Types.BOOLEAN: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.booleanConverter(columnType)) - .toArray(Boolean[]::new)); - case Types.STRUCT: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.structConverter(OBJECT_MAPPER)) - .toArray(Map[]::new)); - case Types.ARRAY: - return new SfSqlArray( - columnSubType, - getStream(nodeElements, converters.arrayConverter(OBJECT_MAPPER)) - .toArray(Map[][]::new)); - default: - throw new SFException( - ErrorCode.FEATURE_UNSUPPORTED, - "Can't construct array for data type: " + columnSubType); - } - } catch (JsonProcessingException e) { - throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); - } - } - - private Object[] convertToFixedArray( - Iterator nodeElements, JsonStringToTypeConverter bigIntConverter) { - AtomicInteger bigDecimalCount = new AtomicInteger(); - Object[] elements = - getStream(nodeElements, bigIntConverter) - .peek( - elem -> { - if (elem instanceof BigDecimal) { - bigDecimalCount.incrementAndGet(); - } - }) - .toArray( - size -> { - boolean shouldbbeReturnAsBigDecimal = bigDecimalCount.get() > 0; - Class returnedClass = - shouldbbeReturnAsBigDecimal ? BigDecimal.class : Long.class; - return java.lang.reflect.Array.newInstance(returnedClass, size); - }); - return elements; - } - - private Stream getStream(Iterator nodeElements, JsonStringToTypeConverter converter) { - return StreamSupport.stream( - Spliterators.spliteratorUnknownSize(nodeElements, Spliterator.ORDERED), false) - .map( - elem -> { - try { - return convert(converter, (JsonNode) elem); - } catch (SFException e) { - throw new RuntimeException(e); - } - }); - } - - private static Object convert(JsonStringToTypeConverter converter, JsonNode node) - throws SFException { - String nodeValue = getJsonNodeStringValue(node); - return converter.convert(nodeValue); - } - - @Override - public Date convertToDate(Object object, TimeZone tz) throws SFException { - return (Date) converters.dateConverter(session).convert((String) object); - } - - @Override - public Time convertToTime(Object object, int scale) throws SFException { - return (Time) converters.timeConverter(session).convert((String) object); - } - - @Override - public Timestamp convertToTimestamp( - Object object, int columnType, int columnSubType, TimeZone tz, int scale) throws SFException { - return (Timestamp) - converters - .timestampConverter(columnSubType, columnType, scale, session, null, tz) - .convert((String) object); - } } diff --git a/src/main/java/net/snowflake/client/core/arrow/ArrayConverter.java b/src/main/java/net/snowflake/client/core/arrow/ArrayConverter.java new file mode 100644 index 000000000..08ce23eec --- /dev/null +++ b/src/main/java/net/snowflake/client/core/arrow/ArrayConverter.java @@ -0,0 +1,26 @@ +package net.snowflake.client.core.arrow; + +import net.snowflake.client.core.DataConversionContext; +import net.snowflake.client.core.SFException; +import net.snowflake.client.jdbc.SnowflakeType; +import org.apache.arrow.vector.complex.ListVector; + +public class ArrayConverter extends AbstractArrowVectorConverter { + + private final ListVector vector; + + public ArrayConverter(ListVector valueVector, int vectorIndex, DataConversionContext context) { + super(SnowflakeType.ARRAY.name(), valueVector, vectorIndex, context); + this.vector = valueVector; + } + + @Override + public Object toObject(int index) throws SFException { + return vector.getObject(index); + } + + @Override + public String toString(int index) throws SFException { + return vector.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 index c5ff2d19b..a07e583ea 100644 --- a/src/main/java/net/snowflake/client/core/arrow/StructuredTypeDateTimeConverter.java +++ b/src/main/java/net/snowflake/client/core/arrow/StructuredTypeDateTimeConverter.java @@ -12,6 +12,7 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; +import java.util.Map; import java.util.TimeZone; import net.snowflake.client.core.SFException; import net.snowflake.client.core.SnowflakeJdbcInternalApi; @@ -46,11 +47,7 @@ public StructuredTypeDateTimeConverter( } public Timestamp getTimestamp( - JsonStringHashMap obj, - int columnType, - int columnSubType, - TimeZone tz, - int scale) + Map obj, int columnType, int columnSubType, TimeZone tz, int scale) throws SFException { if (tz == null) { tz = TimeZone.getDefault(); 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 e53771639..8212e5830 100644 --- a/src/main/java/net/snowflake/client/core/json/BytesConverter.java +++ b/src/main/java/net/snowflake/client/core/json/BytesConverter.java @@ -11,7 +11,7 @@ public class BytesConverter { private final Converters converters; - BytesConverter(Converters converters) { + public BytesConverter(Converters converters) { this.converters = converters; } 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 cfdc532d4..a2f911b39 100644 --- a/src/main/java/net/snowflake/client/core/json/Converters.java +++ b/src/main/java/net/snowflake/client/core/json/Converters.java @@ -18,7 +18,7 @@ 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; +import net.snowflake.client.util.Converter; import net.snowflake.common.core.SFBinaryFormat; import net.snowflake.common.core.SFTimestamp; import net.snowflake.common.core.SnowflakeDateTimeFormat; @@ -122,47 +122,47 @@ public StructuredTypeDateTimeConverter getStructuredTypeDateTimeConverter() { } @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter integerConverter(int columnType) { + public Converter integerConverter(int columnType) { return value -> getNumberConverter().getInt(value, columnType); } @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter smallIntConverter(int columnType) { + public Converter smallIntConverter(int columnType) { return value -> getNumberConverter().getShort(value, columnType); } @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter tinyIntConverter(int columnType) { + public Converter tinyIntConverter(int columnType) { return value -> getNumberConverter().getByte(value); } @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter bigIntConverter(int columnType) { + public Converter bigIntConverter(int columnType) { return value -> getNumberConverter().getBigInt(value, columnType); } @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter longConverter(int columnType) { + public Converter longConverter(int columnType) { return value -> getNumberConverter().getLong(value, columnType); } @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter bigDecimalConverter(int columnType) { + public Converter bigDecimalConverter(int columnType) { return value -> getNumberConverter().getBigDecimal(value, columnType); } @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter floatConverter(int columnType) { + public Converter floatConverter(int columnType) { return value -> getNumberConverter().getBigDecimal(value, columnType); } @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter doubleConverter(int columnType) { + public Converter doubleConverter(int columnType) { return value -> getNumberConverter().getBigDecimal(value, columnType); } @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter bytesConverter(int columnType, int scale) { + public Converter bytesConverter(int columnType, int scale) { return value -> { byte[] primitiveArray = getBytesConverter().getBytes(value, columnType, Types.BINARY, scale); Byte[] newByteArray = new Byte[primitiveArray.length]; @@ -172,41 +172,50 @@ public JsonStringToTypeConverter bytesConverter(int columnType, int scale) { } @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter varcharConverter(int columnType, int columnSubType, int scale) { + public Converter varcharConverter(int columnType, int columnSubType, int scale) { return value -> getStringConverter().getString(value, columnType, columnSubType, scale); } @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter booleanConverter(int columnType) { + public Converter booleanConverter(int columnType) { return value -> getBooleanConverter().getBoolean(value, columnType); } @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter dateConverter(SFBaseSession session) { + public Converter dateStringConverter(SFBaseSession session) { return value -> { SnowflakeDateTimeFormat formatter = SnowflakeDateTimeFormat.fromSqlFormat( (String) session.getCommonParameters().get("DATE_OUTPUT_FORMAT")); - SFTimestamp timestamp = formatter.parse(value); + SFTimestamp timestamp = formatter.parse((String) value); return Date.valueOf( Instant.ofEpochMilli(timestamp.getTime()).atZone(ZoneOffset.UTC).toLocalDate()); }; } @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter timeConverter(SFBaseSession session) { + public Converter dateFromIntConverter(TimeZone tz) { + return value -> structuredTypeDateTimeConverter.getDate((Integer) value, tz); + } + + @SnowflakeJdbcInternalApi + public Converter timeFromStringConverter(SFBaseSession session) { return value -> { SnowflakeDateTimeFormat formatter = SnowflakeDateTimeFormat.fromSqlFormat( (String) session.getCommonParameters().get("TIME_OUTPUT_FORMAT")); - SFTimestamp timestamp = formatter.parse(value); + SFTimestamp timestamp = formatter.parse((String) value); return Time.valueOf( Instant.ofEpochMilli(timestamp.getTime()).atZone(ZoneOffset.UTC).toLocalTime()); }; } + public Converter timeFromIntConverter(int scale) { + return value -> structuredTypeDateTimeConverter.getTime((Long) value, scale); + } + @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter timestampConverter( + public Converter timestampFromStringConverter( int columnSubType, int columnType, int scale, @@ -225,11 +234,18 @@ public JsonStringToTypeConverter timestampConverter( }; } + public Converter timestampFromStructConverter( + int columnType, int columnSubType, TimeZone tz, int scale) { + return value -> + structuredTypeDateTimeConverter.getTimestamp( + (Map) value, columnType, columnSubType, tz, scale); + } + @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter structConverter(ObjectMapper objectMapper) { + public Converter structConverter(ObjectMapper objectMapper) { return value -> { try { - return objectMapper.readValue(value, Map.class); + return objectMapper.readValue((String) value, Map.class); } catch (JsonProcessingException e) { throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); } @@ -237,10 +253,10 @@ public JsonStringToTypeConverter structConverter(ObjectMapper objectMapper) { } @SnowflakeJdbcInternalApi - public JsonStringToTypeConverter arrayConverter(ObjectMapper objectMapper) { + public Converter arrayConverter(ObjectMapper objectMapper) { return value -> { try { - return objectMapper.readValue(value, Map[].class); + return objectMapper.readValue((String) value, Map[].class); } catch (JsonProcessingException e) { throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); } diff --git a/src/main/java/net/snowflake/client/core/structs/StructureTypeHelper.java b/src/main/java/net/snowflake/client/core/structs/StructureTypeHelper.java index 46e8bb2e8..5718573e2 100644 --- a/src/main/java/net/snowflake/client/core/structs/StructureTypeHelper.java +++ b/src/main/java/net/snowflake/client/core/structs/StructureTypeHelper.java @@ -5,8 +5,8 @@ @SnowflakeJdbcInternalApi public class StructureTypeHelper { private static final String STRUCTURED_TYPE_ENABLED_PROPERTY_NAME = "STRUCTURED_TYPE_ENABLED"; - private static boolean structuredTypeEnabled = - Boolean.valueOf(System.getProperty(STRUCTURED_TYPE_ENABLED_PROPERTY_NAME)); + private static boolean structuredTypeEnabled = true; + // Boolean.valueOf(System.getProperty(STRUCTURED_TYPE_ENABLED_PROPERTY_NAME)); public static boolean isStructureTypeEnabled() { return structuredTypeEnabled; diff --git a/src/main/java/net/snowflake/client/jdbc/ArrowResultChunk.java b/src/main/java/net/snowflake/client/jdbc/ArrowResultChunk.java index 123eb0139..96cc0aab7 100644 --- a/src/main/java/net/snowflake/client/jdbc/ArrowResultChunk.java +++ b/src/main/java/net/snowflake/client/jdbc/ArrowResultChunk.java @@ -13,6 +13,7 @@ import net.snowflake.client.core.DataConversionContext; import net.snowflake.client.core.SFBaseSession; import net.snowflake.client.core.SFException; +import net.snowflake.client.core.arrow.ArrayConverter; import net.snowflake.client.core.arrow.ArrowResultChunkIndexSorter; import net.snowflake.client.core.arrow.ArrowVectorConverter; import net.snowflake.client.core.arrow.BigIntToFixedConverter; @@ -56,6 +57,7 @@ import org.apache.arrow.vector.VarBinaryVector; import org.apache.arrow.vector.VarCharVector; import org.apache.arrow.vector.VectorSchemaRoot; +import org.apache.arrow.vector.complex.ListVector; import org.apache.arrow.vector.complex.MapVector; import org.apache.arrow.vector.complex.StructVector; import org.apache.arrow.vector.ipc.ArrowStreamReader; @@ -201,7 +203,6 @@ private static List initConverters( SnowflakeType st = SnowflakeType.valueOf(customMeta.get("logicalType")); switch (st) { case ANY: - case ARRAY: case CHAR: case TEXT: case VARIANT: @@ -212,6 +213,14 @@ private static List initConverters( converters.add(new MapConverter((MapVector) vector, i, context)); break; + case ARRAY: + if (vector instanceof ListVector) { + converters.add(new ArrayConverter((ListVector) vector, i, context)); + } else { + converters.add(new VarCharConverter(vector, i, context)); + } + break; + case OBJECT: if (vector instanceof StructVector) { converters.add(new StructConverter((StructVector) vector, i, context)); diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java index 0cc0c225e..be8a2783d 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java @@ -1415,130 +1415,130 @@ public T[] getArray(int columnIndex, Class type) throws SQLException { T[] arr = (T[]) java.lang.reflect.Array.newInstance(type, objects.length); int counter = 0; for (Object value : objects) { - if (SQLData.class.isAssignableFrom(type)) { - Map data = (Map) value; - SQLData instance = (SQLData) SQLDataCreationHelper.create(type); - SQLInput sqlInput = - new JsonSqlInput( - OBJECT_MAPPER.convertValue(data, JsonNode.class), - session, - sfBaseResultSet.getConverters(), - sfBaseResultSet.getMetaData().getColumnMetadata().get(columnIndex - 1).getFields(), - sfBaseResultSet.getSessionTimezone()); - instance.readSQL(sqlInput, null); - arr[counter++] = (T) instance; - } else if (String.class.isAssignableFrom(type)) { - arr[counter++] = - mapSFExceptionToSQLException( - () -> - (T) - sfBaseResultSet - .getConverters() - .getStringConverter() - .getString(value, columnType, columnSubType, scale)); - } else if (Boolean.class.isAssignableFrom(type)) { - arr[counter++] = - mapSFExceptionToSQLException( - () -> - (T) - sfBaseResultSet - .getConverters() - .getBooleanConverter() - .getBoolean(value, columnType)); - } else if (Byte.class.isAssignableFrom(type)) { - arr[counter++] = - mapSFExceptionToSQLException( - () -> - (T) - sfBaseResultSet - .getConverters() - .getBytesConverter() - .getBytes(value, columnType, columnSubType, scale)); - } else if (Short.class.isAssignableFrom(type)) { - arr[counter++] = - mapSFExceptionToSQLException( - () -> - (T) - Short.valueOf( - sfBaseResultSet - .getConverters() - .getNumberConverter() - .getShort(value, columnType))); - } else if (Integer.class.isAssignableFrom(type)) { - arr[counter++] = - mapSFExceptionToSQLException( - () -> - (T) - Integer.valueOf( - sfBaseResultSet - .getConverters() - .getNumberConverter() - .getInt(value, columnType))); - } else if (Long.class.isAssignableFrom(type)) { - arr[counter++] = - mapSFExceptionToSQLException( - () -> - (T) - Long.valueOf( - sfBaseResultSet - .getConverters() - .getNumberConverter() - .getLong(value, columnType))); - } else if (Float.class.isAssignableFrom(type)) { - arr[counter++] = - mapSFExceptionToSQLException( - () -> - (T) - Float.valueOf( - sfBaseResultSet - .getConverters() - .getNumberConverter() - .getFloat(value, columnType))); - } else if (Double.class.isAssignableFrom(type)) { - arr[counter++] = - mapSFExceptionToSQLException( - () -> - (T) - Double.valueOf( - sfBaseResultSet - .getConverters() - .getNumberConverter() - .getDouble(value, columnType))); - } else if (Date.class.isAssignableFrom(type)) { - arr[counter++] = - mapSFExceptionToSQLException( - () -> - (T) - sfBaseResultSet - .getConverters() - .dateConverter(session) - .convert((String) value)); - } else if (Time.class.isAssignableFrom(type)) { - arr[counter++] = - mapSFExceptionToSQLException( - () -> - (T) - sfBaseResultSet - .getConverters() - .timeConverter(session) - .convert((String) value)); - } else if (Timestamp.class.isAssignableFrom(type)) { - mapSFExceptionToSQLException( - () -> - (T) - sfBaseResultSet - .getConverters() - .timestampConverter(columnSubType, columnType, scale, session, null, tz) - .convert((String) value)); - } else if (BigDecimal.class.isAssignableFrom(type)) { - arr[counter++] = (T) getBigDecimal(columnIndex); + if (type.isAssignableFrom(value.getClass())) { + arr[counter++] = (T) value; } else { - logger.debug( - "Unsupported type passed to getArray(int columnIndex, Class type): " - + type.getName()); - throw new SQLException( - "Type passed to 'getObject(int columnIndex,Class type)' is unsupported. Type: " - + type.getName()); + if (SQLData.class.isAssignableFrom(type)) { + SQLData instance = (SQLData) SQLDataCreationHelper.create(type); + SQLInput sqlInput = + sfBaseResultSet.createSqlInputForColumn( + value, objects.getClass(), columnIndex, session); + instance.readSQL(sqlInput, null); + arr[counter++] = (T) instance; + } else if (String.class.isAssignableFrom(type)) { + arr[counter++] = + mapSFExceptionToSQLException( + () -> + (T) + sfBaseResultSet + .getConverters() + .getStringConverter() + .getString(value, columnType, columnSubType, scale)); + } else if (Boolean.class.isAssignableFrom(type)) { + arr[counter++] = + mapSFExceptionToSQLException( + () -> + (T) + sfBaseResultSet + .getConverters() + .getBooleanConverter() + .getBoolean(value, columnType)); + } else if (Byte.class.isAssignableFrom(type)) { + arr[counter++] = + mapSFExceptionToSQLException( + () -> + (T) + sfBaseResultSet + .getConverters() + .getBytesConverter() + .getBytes(value, columnType, columnSubType, scale)); + } else if (Short.class.isAssignableFrom(type)) { + arr[counter++] = + mapSFExceptionToSQLException( + () -> + (T) + Short.valueOf( + sfBaseResultSet + .getConverters() + .getNumberConverter() + .getShort(value, columnType))); + } else if (Integer.class.isAssignableFrom(type)) { + arr[counter++] = + mapSFExceptionToSQLException( + () -> + (T) + Integer.valueOf( + sfBaseResultSet + .getConverters() + .getNumberConverter() + .getInt(value, columnType))); + } else if (Long.class.isAssignableFrom(type)) { + arr[counter++] = + mapSFExceptionToSQLException( + () -> + (T) + Long.valueOf( + sfBaseResultSet + .getConverters() + .getNumberConverter() + .getLong(value, columnType))); + } else if (Float.class.isAssignableFrom(type)) { + arr[counter++] = + mapSFExceptionToSQLException( + () -> + (T) + Float.valueOf( + sfBaseResultSet + .getConverters() + .getNumberConverter() + .getFloat(value, columnType))); + } else if (Double.class.isAssignableFrom(type)) { + arr[counter++] = + mapSFExceptionToSQLException( + () -> + (T) + Double.valueOf( + sfBaseResultSet + .getConverters() + .getNumberConverter() + .getDouble(value, columnType))); + } else if (Date.class.isAssignableFrom(type)) { + arr[counter++] = + mapSFExceptionToSQLException( + () -> + (T) + sfBaseResultSet + .getConverters() + .dateStringConverter(session) + .convert((String) value)); + } else if (Time.class.isAssignableFrom(type)) { + arr[counter++] = + mapSFExceptionToSQLException( + () -> + (T) + sfBaseResultSet + .getConverters() + .timeFromStringConverter(session) + .convert((String) value)); + } else if (Timestamp.class.isAssignableFrom(type)) { + mapSFExceptionToSQLException( + () -> + (T) + sfBaseResultSet + .getConverters() + .timestampFromStringConverter( + columnSubType, columnType, scale, session, null, tz) + .convert((String) value)); + } else if (BigDecimal.class.isAssignableFrom(type)) { + arr[counter++] = (T) getBigDecimal(columnIndex); + } else { + logger.debug( + "Unsupported type passed to getArray(int columnIndex, Class type): " + + type.getName()); + throw new SQLException( + "Type passed to 'getObject(int columnIndex,Class type)' is unsupported. Type: " + + type.getName()); + } } } return arr; @@ -1558,36 +1558,9 @@ public Map getMap(int columnIndex, Class type) throws SQLExcep for (Map.Entry entry : map.entrySet()) { if (SQLData.class.isAssignableFrom(type)) { SQLData instance = (SQLData) SQLDataCreationHelper.create(type); - SQLInput sqlInput; - if (object instanceof JsonSqlInput) { - sqlInput = - new JsonSqlInput( - (JsonNode) entry.getValue(), - session, - sfBaseResultSet.getConverters(), - sfBaseResultSet - .getMetaData() - .getColumnMetadata() - .get(columnIndex - 1) - .getFields(), - sfBaseResultSet.getSessionTimezone()); - } else if (object instanceof ArrowSqlInput || object instanceof Map) { - sqlInput = - new ArrowSqlInput( - (Map) entry.getValue(), - session, - sfBaseResultSet.getConverters(), - sfBaseResultSet - .getMetaData() - .getColumnMetadata() - .get(columnIndex - 1) - .getFields()); - } else { - throw new SQLException( - "SqlInput type " - + object.getClass() - + " is not supported when mapping to SQLData class"); - } + SQLInput sqlInput = + sfBaseResultSet.createSqlInputForColumn( + entry.getValue(), object.getClass(), columnIndex, session); instance.readSQL(sqlInput, null); resultMap.put(entry.getKey(), (T) instance); } else if (String.class.isAssignableFrom(type)) { diff --git a/src/main/java/net/snowflake/client/util/JsonStringToTypeConverter.java b/src/main/java/net/snowflake/client/util/Converter.java similarity index 61% rename from src/main/java/net/snowflake/client/util/JsonStringToTypeConverter.java rename to src/main/java/net/snowflake/client/util/Converter.java index 37e6aab3e..8547be5b2 100644 --- a/src/main/java/net/snowflake/client/util/JsonStringToTypeConverter.java +++ b/src/main/java/net/snowflake/client/util/Converter.java @@ -6,9 +6,9 @@ import net.snowflake.client.core.SFException; import net.snowflake.client.core.SnowflakeJdbcInternalApi; -/** Functional interface used to convert json data to expected type */ +/** Functional interface used to convert data to expected type */ @SnowflakeJdbcInternalApi @FunctionalInterface -public interface JsonStringToTypeConverter { - T convert(String string) throws SFException; +public interface Converter { + T convert(Object object) throws SFException; } diff --git a/src/main/resources/net/snowflake/client/jdbc/jdbc_error_messages.properties b/src/main/resources/net/snowflake/client/jdbc/jdbc_error_messages.properties index 17dee4af3..5d9973bb1 100644 --- a/src/main/resources/net/snowflake/client/jdbc/jdbc_error_messages.properties +++ b/src/main/resources/net/snowflake/client/jdbc/jdbc_error_messages.properties @@ -80,5 +80,5 @@ Error message={3}, Extended error info={4} 200058=Value is too large to be stored as integer at batch index {0}. Use executeLargeBatch() instead. 200059=Invalid Connect String: {0}. 200061=GCS operation failed: Operation={0}, Error code={1}, Message={2}, Reason={3} -200063= Invalid json - Cannot be parsed and converted to structured type. +200063=Invalid data - Cannot be parsed and converted to structured type. diff --git a/src/test/java/net/snowflake/client/jdbc/ArrowResultSetStructuredTypesLatestIT.java b/src/test/java/net/snowflake/client/jdbc/ArrowResultSetStructuredTypesLatestIT.java deleted file mode 100644 index 34611a524..000000000 --- a/src/test/java/net/snowflake/client/jdbc/ArrowResultSetStructuredTypesLatestIT.java +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (c) 2012-2024 Snowflake Computing Inc. All right reserved. - */ -package net.snowflake.client.jdbc; - -public class ArrowResultSetStructuredTypesLatestIT extends ResultSetStructuredTypesLatestIT { - public ArrowResultSetStructuredTypesLatestIT() { - 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 deleted file mode 100644 index 7772c6b03..000000000 --- a/src/test/java/net/snowflake/client/jdbc/NativeArrowResultSetStructuredTypesLatestIT.java +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 81833c8dc..71f8d590b 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetStructuredTypesLatestIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetStructuredTypesLatestIT.java @@ -28,20 +28,28 @@ 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; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +@RunWith(Parameterized.class) @Category(TestCategoryStructuredType.class) public class ResultSetStructuredTypesLatestIT extends BaseJDBCTest { - private final ResultSetFormatType queryResultFormat; - public ResultSetStructuredTypesLatestIT() { - this(ResultSetFormatType.JSON); + @Parameterized.Parameters(name = "format={0}") + public static Object[][] data() { + return new Object[][] { + {ResultSetFormatType.JSON}, + {ResultSetFormatType.ARROW_WITH_JSON_STRUCTURED_TYPES}, + {ResultSetFormatType.NATIVE_ARROW} + }; } - protected ResultSetStructuredTypesLatestIT(ResultSetFormatType queryResultFormat) { + private final ResultSetFormatType queryResultFormat; + + public ResultSetStructuredTypesLatestIT(ResultSetFormatType queryResultFormat) { this.queryResultFormat = queryResultFormat; } @@ -184,7 +192,6 @@ public void testMapJsonToMap() throws SQLException { @Test 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))", @@ -199,7 +206,6 @@ public void testReturnAsArrayOfSqlData() throws SQLException { @Test public void testReturnAsArrayOfString() throws SQLException { - Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT ARRAY_CONSTRUCT('one', 'two','three')::ARRAY(VARCHAR)", (resultSet) -> { @@ -213,7 +219,6 @@ public void testReturnAsArrayOfString() throws SQLException { @Test public void testReturnAsListOfIntegers() throws SQLException { - Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT ARRAY_CONSTRUCT(1,2,3)::ARRAY(INTEGER)", (resultSet) -> { @@ -316,7 +321,6 @@ public void testReturnAsMapOfBoolean() throws SQLException { @Test 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))", @@ -342,7 +346,6 @@ public void testMapStructsFromChunks() throws SQLException { @Test public void testMapIntegerArray() throws SQLException { - Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT ARRAY_CONSTRUCT(10, 20, 30)::ARRAY(INTEGER)", (resultSet) -> { @@ -355,7 +358,6 @@ public void testMapIntegerArray() throws SQLException { @Test public void testMapFixedToLongArray() throws SQLException { - Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT ARRAY_CONSTRUCT(10, 20, 30)::ARRAY(SMALLINT)", (resultSet) -> { @@ -368,7 +370,6 @@ public void testMapFixedToLongArray() throws SQLException { @Test 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(); @@ -390,9 +391,9 @@ public void testMapDecimalArray() throws SQLException { "SELECT ARRAY_CONSTRUCT(10.2, 20.02, 30)::ARRAY(DECIMAL(20,2))"); ) { resultSet.next(); BigDecimal[] resultArray2 = (BigDecimal[]) resultSet.getArray(1).getArray(); - assertEquals(BigDecimal.valueOf(10.2), resultArray2[0]); - assertEquals(BigDecimal.valueOf(20.02), resultArray2[1]); - assertEquals(BigDecimal.valueOf(30), resultArray2[2]); + assertEquals(BigDecimal.valueOf(10.20).doubleValue(), resultArray2[0].doubleValue(), 0); + assertEquals(BigDecimal.valueOf(20.02).doubleValue(), resultArray2[1].doubleValue(), 0); + assertEquals(BigDecimal.valueOf(30.00).doubleValue(), resultArray2[2].doubleValue(), 0); } // when: jdbc_treat_decimal_as_int=false scale=0 @@ -412,7 +413,6 @@ public void testMapDecimalArray() throws SQLException { @Test public void testMapVarcharArray() throws SQLException { - Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT 'text', ARRAY_CONSTRUCT('10', '20','30')::ARRAY(VARCHAR)", (resultSet) -> { @@ -426,45 +426,44 @@ public void testMapVarcharArray() throws SQLException { @Test 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) -> { Date[] resultArray = (Date[]) resultSet.getArray(1).getArray(); - assertEquals(Date.valueOf(LocalDate.of(2023, 12, 24)), resultArray[0]); - assertEquals(Date.valueOf(LocalDate.of(2023, 12, 25)), resultArray[1]); + assertEquals( + Date.valueOf(LocalDate.of(2023, 12, 24)).toString(), resultArray[0].toString()); + assertEquals( + Date.valueOf(LocalDate.of(2023, 12, 25)).toString(), resultArray[1].toString()); }); } @Test 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)", + "SELECT ARRAY_CONSTRUCT(to_time('15:39:20.123'), to_time('09:12:20.123'))::ARRAY(TIME)", (resultSet) -> { Time[] resultArray = (Time[]) resultSet.getArray(1).getArray(); - assertEquals(Time.valueOf(LocalTime.of(15, 39, 20)), resultArray[0]); - assertEquals(Time.valueOf(LocalTime.of(15, 39, 20)), resultArray[1]); + assertEquals( + Time.valueOf(LocalTime.of(15, 39, 20)).toString(), resultArray[0].toString()); + assertEquals(Time.valueOf(LocalTime.of(9, 12, 20)).toString(), resultArray[1].toString()); }); } @Test 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) -> { Timestamp[] resultArray = (Timestamp[]) resultSet.getArray(1).getArray(); assertEquals( - Timestamp.valueOf(LocalDateTime.of(2021, 12, 23, 10, 44, 44)), resultArray[0]); + Timestamp.valueOf(LocalDateTime.of(2021, 12, 23, 9, 44, 44)), resultArray[0]); assertEquals( - Timestamp.valueOf(LocalDateTime.of(2021, 12, 24, 10, 55, 55)), resultArray[1]); + Timestamp.valueOf(LocalDateTime.of(2021, 12, 24, 9, 55, 55)), resultArray[1]); }); } @Test public void testMapBooleanArray() throws SQLException { - Assume.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW); withFirstRow( "SELECT ARRAY_CONSTRUCT(true,false)::ARRAY(BOOLEAN)", (resultSet) -> { @@ -476,7 +475,6 @@ public void testMapBooleanArray() throws SQLException { @Test 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) -> { @@ -488,25 +486,31 @@ public void testMapBinaryArray() throws SQLException { @Test 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) -> { Map[] resultArray = (Map[]) resultSet.getArray(1).getArray(); - assertEquals("{x=abc, y=1}", resultArray[0].toString()); - assertEquals("{x=def, y=2}", resultArray[1].toString()); + Map firstEntry = resultArray[0]; + Map secondEntry = resultArray[1]; + assertEquals(firstEntry.get("x").toString(), "abc"); + assertEquals(firstEntry.get("y").toString(), "1"); + assertEquals(secondEntry.get("x").toString(), "def"); + assertEquals(secondEntry.get("y").toString(), "2"); }); } @Test 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) -> { Map[][] resultArray = (Map[][]) resultSet.getArray(1).getArray(); - assertEquals("{x=abc, y=1}", resultArray[0][0].toString()); - assertEquals("{x=def, y=2}", resultArray[0][1].toString()); + Map firstEntry = resultArray[0][0]; + Map secondEntry = resultArray[0][1]; + assertEquals(firstEntry.get("x").toString(), "abc"); + assertEquals(firstEntry.get("y").toString(), "1"); + assertEquals(secondEntry.get("x").toString(), "def"); + assertEquals(secondEntry.get("y").toString(), "2"); }); }