From c220deb4bea4bb4e9b0bfbe542bf390146dd3e17 Mon Sep 17 00:00:00 2001 From: Przemyslaw Motacki Date: Mon, 13 Nov 2023 07:13:42 +0100 Subject: [PATCH 01/14] StructuredType part 1. --- .../snowflake/client/core/JsonSqlInput.java | 156 ++++++++++++++++++ .../client/core/SFJsonResultSet.java | 30 +++- .../snowflake/client/core/SFResultSet.java | 8 +- .../client/jdbc/JsonResultChunk.java | 28 +++- .../client/jdbc/SnowflakeBaseResultSet.java | 21 ++- .../snowflake/client/jdbc/SnowflakeType.java | 1 + .../snowflake/client/jdbc/SnowflakeUtil.java | 5 +- .../client/jdbc/MockConnectionTest.java | 2 +- .../snowflake/client/jdbc/ResultSet0IT.java | 2 +- .../snowflake/client/jdbc/ResultSetIT.java | 32 ++++ 10 files changed, 268 insertions(+), 17 deletions(-) create mode 100644 src/main/java/net/snowflake/client/core/JsonSqlInput.java diff --git a/src/main/java/net/snowflake/client/core/JsonSqlInput.java b/src/main/java/net/snowflake/client/core/JsonSqlInput.java new file mode 100644 index 000000000..f007b0db3 --- /dev/null +++ b/src/main/java/net/snowflake/client/core/JsonSqlInput.java @@ -0,0 +1,156 @@ +package net.snowflake.client.core; + +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.*; +import java.util.Iterator; + +public class JsonSqlInput implements SQLInput { + + private final JsonNode input; + private final Iterator elements; + + public JsonSqlInput(JsonNode input) { + this.input = input; + elements = input.elements(); + } + + @Override + public String readString() throws SQLException { + return elements.next().textValue(); + } + + @Override + public boolean readBoolean() throws SQLException { + return elements.next().booleanValue(); + } + + @Override + public byte readByte() throws SQLException { + return 0; + } + + @Override + public short readShort() throws SQLException { + return 0; + } + + @Override + public int readInt() throws SQLException { + return 0; + } + + @Override + public long readLong() throws SQLException { + return 0; + } + + @Override + public float readFloat() throws SQLException { + return 0; + } + + @Override + public double readDouble() throws SQLException { + return 0; + } + + @Override + public BigDecimal readBigDecimal() throws SQLException { + return null; + } + + @Override + public byte[] readBytes() throws SQLException { + return new byte[0]; + } + + @Override + public Date readDate() throws SQLException { + return null; + } + + @Override + public Time readTime() throws SQLException { + return null; + } + + @Override + public Timestamp readTimestamp() throws SQLException { + return null; + } + + @Override + public Reader readCharacterStream() throws SQLException { + return null; + } + + @Override + public InputStream readAsciiStream() throws SQLException { + return null; + } + + @Override + public InputStream readBinaryStream() throws SQLException { + return null; + } + + @Override + public Object readObject() throws SQLException { + return null; + } + + @Override + public Ref readRef() throws SQLException { + return null; + } + + @Override + public Blob readBlob() throws SQLException { + return null; + } + + @Override + public Clob readClob() throws SQLException { + return null; + } + + @Override + public Array readArray() throws SQLException { + return null; + } + + @Override + public boolean wasNull() throws SQLException { + return false; + } + + @Override + public URL readURL() throws SQLException { + return null; + } + + @Override + public NClob readNClob() throws SQLException { + return null; + } + + @Override + public String readNString() throws SQLException { + return null; + } + + @Override + public SQLXML readSQLXML() throws SQLException { + return null; + } + + @Override + public RowId readRowId() throws SQLException { + return null; + } +} diff --git a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java index a896388cf..18f11e6d2 100644 --- a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java @@ -7,11 +7,10 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.nio.ByteBuffer; -import java.sql.Date; -import java.sql.Time; -import java.sql.Timestamp; -import java.sql.Types; +import java.sql.*; import java.util.TimeZone; + +import com.fasterxml.jackson.databind.JsonNode; import net.snowflake.client.core.arrow.ArrowResultUtil; import net.snowflake.client.jdbc.*; import net.snowflake.client.log.ArgSupplier; @@ -23,6 +22,8 @@ import net.snowflake.common.core.SFTimestamp; import org.apache.arrow.vector.Float8Vector; +import javax.sql.rowset.serial.SQLInputImpl; + /** 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); @@ -44,11 +45,23 @@ public abstract class SFJsonResultSet extends SFBaseResultSet { */ protected abstract Object getObjectInternal(int columnIndex) throws SFException; +// public getObject(int columnIndex) throws SFException { +// // check metaData +// int type = resultSetMetaData.getColumnType(columnIndex); +// +//// if (typeCanBeConvertToT) +// +// T t = new T(); +// t.readSQL(new SQLInput()); +// +// +// +// } public Object getObject(int columnIndex) throws SFException { int type = resultSetMetaData.getColumnType(columnIndex); - Object obj = getObjectInternal(columnIndex); + Object obj = getObjectInternal(columnIndex); //JsonNode if (obj == null) { return null; } @@ -86,11 +99,18 @@ public Object getObject(int columnIndex) throws SFException { case Types.BOOLEAN: return getBoolean(columnIndex); + case Types.STRUCT: + return getSqlInput((JsonNode) obj); + default: throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, "data type: " + type); } } + private Object getSqlInput(JsonNode input) { + return new JsonSqlInput(input); + } + /** * Sometimes large BIGINTS overflow the java Long type. In these cases, return a BigDecimal type * instead. diff --git a/src/main/java/net/snowflake/client/core/SFResultSet.java b/src/main/java/net/snowflake/client/core/SFResultSet.java index bfcdb266c..7fe44cd3c 100644 --- a/src/main/java/net/snowflake/client/core/SFResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFResultSet.java @@ -306,16 +306,20 @@ protected Object getObjectInternal(int columnIndex) throws SFException { final int internalColumnIndex = columnIndex - 1; Object retValue; if (sortResult) { + // StructuredType should return JsonNode too retValue = firstChunkSortedRowSet[currentChunkRowIndex][internalColumnIndex]; } else if (firstChunkRowset != null) { retValue = - JsonResultChunk.extractCell(firstChunkRowset, currentChunkRowIndex, internalColumnIndex); + JsonResultChunk.extractCell(firstChunkRowset, currentChunkRowIndex, internalColumnIndex, resultSetMetaData); } else if (currentChunk != null) { + // StructuredType should return JsonNode too retValue = currentChunk.getCell(currentChunkRowIndex, internalColumnIndex); } else { throw new SFException(ErrorCode.COLUMN_DOES_NOT_EXIST, columnIndex); } wasNull = retValue == null; + + //TODO must be JsonNode instance return retValue; } @@ -327,7 +331,7 @@ private void sortResultSet() { firstChunkSortedRowSet[rowIdx] = new Object[columnCount]; for (int colIdx = 0; colIdx < columnCount; colIdx++) { firstChunkSortedRowSet[rowIdx][colIdx] = - JsonResultChunk.extractCell(firstChunkRowset, rowIdx, colIdx); + JsonResultChunk.extractCell(firstChunkRowset, rowIdx, colIdx, resultSetMetaData); } } diff --git a/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java b/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java index a486fa15e..8b64cfc1f 100644 --- a/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java +++ b/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java @@ -4,14 +4,21 @@ package net.snowflake.client.jdbc; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import java.lang.ref.SoftReference; import java.nio.charset.StandardCharsets; +import java.sql.Types; import java.util.ArrayList; import java.util.BitSet; import java.util.LinkedList; import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; +import net.snowflake.client.core.ObjectMapperFactory; import net.snowflake.client.core.SFBaseSession; +import net.snowflake.client.core.SFException; +import net.snowflake.client.core.SFResultSetMetaData; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; import net.snowflake.common.core.SqlState; @@ -19,6 +26,8 @@ public class JsonResultChunk extends SnowflakeResultChunk { private static final SFLogger logger = SFLoggerFactory.getLogger(JsonResultChunk.class); + private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper(); + private ResultChunkData data; private int currentRow; @@ -32,15 +41,30 @@ public JsonResultChunk( this.session = session; } - public static Object extractCell(JsonNode resultData, int rowIdx, int colIdx) { + public static Object extractCell(JsonNode resultData, int rowIdx, int colIdx, SFResultSetMetaData resultSetMetaData) { JsonNode currentRow = resultData.get(rowIdx); JsonNode colNode = currentRow.get(colIdx); if (colNode.isTextual()) { - return colNode.asText(); + try { + if (resultSetMetaData.getColumnType(colIdx + 1) == Types.STRUCT) { + return OBJECT_MAPPER.readTree(colNode.textValue()); + } else { + return colNode.asText(); + } + } //TODO structuredType clear exceptions + catch (SFException e) { + throw new RuntimeException(e); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } } else if (colNode.isNumber()) { return colNode.numberValue(); + } + // TODO: structuredType - need response with json but not as String + else if (colNode.isObject()) { + return colNode; } else if (colNode.isNull()) { return null; } diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java index 521a00f07..e9d246c8b 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java @@ -19,6 +19,8 @@ import net.snowflake.client.log.SFLoggerFactory; import net.snowflake.common.core.SqlState; +import javax.sql.rowset.serial.SQLInputImpl; + /** Base class for query result set and metadata result set */ public abstract class SnowflakeBaseResultSet implements ResultSet { static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeBaseResultSet.class); @@ -1319,15 +1321,26 @@ public void updateNClob(String columnLabel, Reader reader) throws SQLException { // @Override public T getObject(int columnIndex, Class type) throws SQLException { logger.debug("public T getObject(int columnIndex,Class type)", false); - - throw new SnowflakeLoggedFeatureNotSupportedException(session); + if (SQLData.class.isAssignableFrom(type)) { + try { + SQLData instance = (SQLData) type.newInstance(); + SQLInput sqlInput = (SQLInput) getObject(columnIndex); + instance.readSQL(sqlInput, null); + return (T) instance; + } //TODO structuredType clean exceptions + catch (Exception e) { + throw new RuntimeException(e); + } + } else { + return (T) getObject(columnIndex); + } } // @Override public T getObject(String columnLabel, Class type) throws SQLException { logger.debug("public T getObject(String columnLabel,Class type)", false); - - throw new SnowflakeLoggedFeatureNotSupportedException(session); + return getObject(findColumn(columnLabel), type); +// throw new SnowflakeLoggedFeatureNotSupportedException(session); } @SuppressWarnings("unchecked") diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java index 969373896..8bd1e1b58 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java @@ -48,6 +48,7 @@ public static SnowflakeType fromString(String name) { } public static JavaDataType getJavaType(SnowflakeType type) { + //TODO structuredType 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 7a25e1b0d..703032a12 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java @@ -223,8 +223,9 @@ public static SnowflakeColumnMetadata extractColumnMetadata( break; case OBJECT: - colType = Types.VARCHAR; - extColTypeName = "OBJECT"; + colType = Types.STRUCT; + //TODO : structuredType + extColTypeName = "STRUCT"; break; case VARIANT: diff --git a/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java b/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java index 49bc6184f..887b3390c 100644 --- a/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java +++ b/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java @@ -619,7 +619,7 @@ public boolean next() { @Override protected Object getObjectInternal(int columnIndex) { - return JsonResultChunk.extractCell(resultJson, currentRowIdx, columnIndex - 1); + return JsonResultChunk.extractCell(resultJson, currentRowIdx, columnIndex - 1, resultSetMetaData); } @Override diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSet0IT.java b/src/test/java/net/snowflake/client/jdbc/ResultSet0IT.java index 2ce20192a..92f7e0865 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSet0IT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSet0IT.java @@ -54,7 +54,7 @@ public Connection init(Properties paramProperties) throws SQLException { return conn; } - @Before +// @Before public void setUp() throws SQLException { Connection con = init(); diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java index cb79ef7d2..66111f272 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java @@ -49,6 +49,38 @@ public void testFindColumn() throws SQLException { connection.close(); } + public static class TestClass implements SQLData{ + + private String x; + @Override + public String getSQLTypeName() throws SQLException { + return null; + } + + @Override + public void readSQL(SQLInput stream, String typeName) throws SQLException { + x = stream.readString(); + } + + @Override + public void writeSQL(SQLOutput stream) throws SQLException { + + } + } + + @Test + public void testMapJson() throws SQLException { + + Connection connection = init(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("select {'x':'a'}::OBJECT(x VARCHAR)"); + resultSet.next(); + TestClass object = resultSet.getObject(1, TestClass.class); + assertEquals("a", object.x); + statement.close(); + connection.close(); + } + @Test public void testGetColumnClassNameForBinary() throws Throwable { Connection connection = init(); From 9008acbeb504abb8c99abb2ad10498ba816475f6 Mon Sep 17 00:00:00 2001 From: Piotr Fus Date: Mon, 13 Nov 2023 13:53:57 +0100 Subject: [PATCH 02/14] Uncomment ignored @Before --- src/test/java/net/snowflake/client/jdbc/ResultSet0IT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSet0IT.java b/src/test/java/net/snowflake/client/jdbc/ResultSet0IT.java index 92f7e0865..2ce20192a 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSet0IT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSet0IT.java @@ -54,7 +54,7 @@ public Connection init(Properties paramProperties) throws SQLException { return conn; } -// @Before + @Before public void setUp() throws SQLException { Connection con = init(); From 12a735e8cca21cbefc4d8fb1a866c416a1c8a4b2 Mon Sep 17 00:00:00 2001 From: Piotr Fus Date: Tue, 14 Nov 2023 08:05:10 +0100 Subject: [PATCH 03/14] Fix ResultSetLatestIT --- .../snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java index 03f59d6b6..733b46a22 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java @@ -106,10 +106,11 @@ public boolean isCaseSensitive(int column) throws SQLException { int colType = getColumnType(column); switch (colType) { - // Note: SF types ARRAY, OBJECT, GEOGRAPHY, GEOMETRY are also represented as - // VARCHAR. + // TODO structuredType how about other types? + // Note: SF types ARRAY, OBJECT, GEOMETRY are also represented as VARCHAR. case Types.VARCHAR: case Types.CHAR: + case Types.STRUCT: return true; case Types.INTEGER: From 930a875443f0bab985a517cddf7db5ba84ee8c35 Mon Sep 17 00:00:00 2001 From: Piotr Fus Date: Tue, 14 Nov 2023 08:06:50 +0100 Subject: [PATCH 04/14] Fix ResultSetFeatureNotSUpportedIT part 1 --- .../snowflake/client/jdbc/ResultSetFeatureNotSupportedIT.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetFeatureNotSupportedIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetFeatureNotSupportedIT.java index 95e04c8e0..977a2f206 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetFeatureNotSupportedIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetFeatureNotSupportedIT.java @@ -125,7 +125,6 @@ private void checkFeatureNotSupportedException(ResultSet resultSet) throws SQLEx expectFeatureNotSupportedException(() -> resultSet.getNString(1)); expectFeatureNotSupportedException(() -> resultSet.getNCharacterStream(1)); expectFeatureNotSupportedException(() -> resultSet.getNClob(1)); - expectFeatureNotSupportedException(() -> resultSet.getObject(1, String.class)); expectFeatureNotSupportedException(() -> resultSet.updateRef(1, new FakeRef())); expectFeatureNotSupportedException(() -> resultSet.updateBlob(1, new FakeBlob())); From db591545840b899a25179e0576d88135a7939ce1 Mon Sep 17 00:00:00 2001 From: Piotr Fus Date: Tue, 14 Nov 2023 08:19:46 +0100 Subject: [PATCH 05/14] Fix RestulSetArrowIT --- .../client/core/SFArrowResultSet.java | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java index 5a121086a..78ecd4649 100644 --- a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java @@ -10,11 +10,12 @@ import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; -import java.sql.Date; -import java.sql.SQLException; -import java.sql.Time; -import java.sql.Timestamp; +import java.sql.*; import java.util.TimeZone; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import net.snowflake.client.core.arrow.ArrowVectorConverter; import net.snowflake.client.jdbc.*; import net.snowflake.client.jdbc.ArrowResultChunk.ArrowChunkIterator; @@ -31,7 +32,8 @@ /** Arrow result set implementation */ public class SFArrowResultSet extends SFBaseResultSet implements DataConversionContext { - static final SFLogger logger = SFLoggerFactory.getLogger(SFArrowResultSet.class); + private static final SFLogger logger = SFLoggerFactory.getLogger(SFArrowResultSet.class); + private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper(); /** iterator over current arrow result chunk */ private ArrowChunkIterator currentChunkIterator; @@ -477,7 +479,17 @@ public Object getObject(int columnIndex) throws SFException { converter.setTreatNTZAsUTC(treatNTZAsUTC); converter.setUseSessionTimezone(useSessionTimezone); converter.setSessionTimeZone(timeZone); - return converter.toObject(index); + Object obj = converter.toObject(index); + // TODO structuredType clean this up + if (resultSetMetaData.getColumnType(columnIndex) == Types.STRUCT) { + try { + JsonNode jsonNode = OBJECT_MAPPER.readTree((String) obj); + return new JsonSqlInput(jsonNode); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + return obj; } @Override From 240ac15f624a1cafe3158d830e8eefc50b980200 Mon Sep 17 00:00:00 2001 From: Piotr Fus Date: Tue, 14 Nov 2023 11:15:04 +0100 Subject: [PATCH 06/14] Fix ResultSetFeatureNotSupportedIT --- .../snowflake/client/jdbc/ResultSetFeatureNotSupportedIT.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetFeatureNotSupportedIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetFeatureNotSupportedIT.java index 977a2f206..539784120 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetFeatureNotSupportedIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetFeatureNotSupportedIT.java @@ -165,7 +165,6 @@ private void checkFeatureNotSupportedException(ResultSet resultSet) throws SQLEx expectFeatureNotSupportedException(() -> resultSet.getNString("col2")); expectFeatureNotSupportedException(() -> resultSet.getNCharacterStream("col2")); expectFeatureNotSupportedException(() -> resultSet.getNClob("col2")); - expectFeatureNotSupportedException(() -> resultSet.getObject("col2", String.class)); expectFeatureNotSupportedException(() -> resultSet.updateRef("col2", new FakeRef())); expectFeatureNotSupportedException(() -> resultSet.updateBlob("col2", new FakeBlob())); From f9fada6a36aa4beb2dc30da1bf53c42492f1e0ff Mon Sep 17 00:00:00 2001 From: Piotr Fus Date: Tue, 14 Nov 2023 11:24:56 +0100 Subject: [PATCH 07/14] Implement structured types in Arrow and add chunk test --- .../snowflake/client/core/SFJsonResultSet.java | 15 ++++++++++++--- .../net/snowflake/client/core/SFResultSet.java | 2 +- .../snowflake/client/jdbc/JsonResultChunk.java | 17 ++--------------- .../snowflake/client/jdbc/SFAsyncResultSet.java | 2 +- .../net/snowflake/client/jdbc/ResultSetIT.java | 16 ++++++++++++++++ 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java index 18f11e6d2..ca8bb6d5b 100644 --- a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java @@ -10,7 +10,10 @@ import java.sql.*; import java.util.TimeZone; +import com.amazonaws.jmespath.ObjectMapperSingleton; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import net.snowflake.client.core.arrow.ArrowResultUtil; import net.snowflake.client.jdbc.*; import net.snowflake.client.log.ArgSupplier; @@ -27,6 +30,7 @@ /** 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 = new ObjectMapper(); // TODO structuredType is it proper init? TimeZone sessionTimeZone; @@ -100,15 +104,20 @@ public Object getObject(int columnIndex) throws SFException { return getBoolean(columnIndex); case Types.STRUCT: - return getSqlInput((JsonNode) obj); + return getSqlInput((String) obj); default: throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, "data type: " + type); } } - private Object getSqlInput(JsonNode input) { - return new JsonSqlInput(input); + private Object getSqlInput(String input) { + try { + JsonNode jsonNode = OBJECT_MAPPER.readTree(input); + return new JsonSqlInput(jsonNode); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } } /** diff --git a/src/main/java/net/snowflake/client/core/SFResultSet.java b/src/main/java/net/snowflake/client/core/SFResultSet.java index 7fe44cd3c..20fcc095d 100644 --- a/src/main/java/net/snowflake/client/core/SFResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFResultSet.java @@ -319,7 +319,7 @@ protected Object getObjectInternal(int columnIndex) throws SFException { } wasNull = retValue == null; - //TODO must be JsonNode instance + //TODO structuredType must be JsonNode instance return retValue; } diff --git a/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java b/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java index 8b64cfc1f..a208586ab 100644 --- a/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java +++ b/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java @@ -26,8 +26,6 @@ public class JsonResultChunk extends SnowflakeResultChunk { private static final SFLogger logger = SFLoggerFactory.getLogger(JsonResultChunk.class); - private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper(); - private ResultChunkData data; private int currentRow; @@ -47,18 +45,7 @@ public static Object extractCell(JsonNode resultData, int rowIdx, int colIdx, SF JsonNode colNode = currentRow.get(colIdx); if (colNode.isTextual()) { - try { - if (resultSetMetaData.getColumnType(colIdx + 1) == Types.STRUCT) { - return OBJECT_MAPPER.readTree(colNode.textValue()); - } else { - return colNode.asText(); - } - } //TODO structuredType clear exceptions - catch (SFException e) { - throw new RuntimeException(e); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } + return colNode.textValue(); } else if (colNode.isNumber()) { return colNode.numberValue(); } @@ -68,7 +55,7 @@ else if (colNode.isObject()) { } else if (colNode.isNull()) { return null; } - throw new RuntimeException("Unknow json type"); + throw new RuntimeException("Unknown json type"); } public void tryReuse(ResultChunkDataCache cache) { diff --git a/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java b/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java index 7fe9ecbbe..b60a4b4fc 100644 --- a/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java +++ b/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java @@ -286,7 +286,7 @@ public ResultSetMetaData getMetaData() throws SQLException { public Object getObject(int columnIndex) throws SQLException { raiseSQLExceptionIfResultSetIsClosed(); - return resultSetForNext.getObject(columnIndex); + return resultSetForNext.getObject(columnIndex); // TODO structuredType does it work out of the box? } public BigDecimal getBigDecimal(int columnIndex) throws SQLException { diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java index 66111f272..5208f8540 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java @@ -81,6 +81,22 @@ public void testMapJson() throws SQLException { connection.close(); } + @Test + public void testMapJsonFromChunks() throws SQLException { + + Connection connection = init(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("select {'x':'a'}::OBJECT(x VARCHAR) FROM TABLE(GENERATOR(ROWCOUNT=>30000))"); + int i = 0; + while (resultSet.next()) { + System.out.println(i++); + TestClass object = resultSet.getObject(1, TestClass.class); + assertEquals("a", object.x); + } + statement.close(); + connection.close(); + } + @Test public void testGetColumnClassNameForBinary() throws Throwable { Connection connection = init(); From 1888ade841be3b7822d1ff6b8fc7ee0754576062 Mon Sep 17 00:00:00 2001 From: Piotr Fus Date: Tue, 14 Nov 2023 11:37:40 +0100 Subject: [PATCH 08/14] Align to checkstyle --- .../snowflake/client/core/JsonSqlInput.java | 285 +++++++++--------- .../client/core/SFArrowResultSet.java | 7 +- .../client/core/SFJsonResultSet.java | 39 ++- .../snowflake/client/core/SFResultSet.java | 5 +- .../client/jdbc/JsonResultChunk.java | 11 +- .../client/jdbc/SFAsyncResultSet.java | 3 +- .../client/jdbc/SnowflakeBaseResultSet.java | 8 +- .../jdbc/SnowflakeResultSetMetaDataV1.java | 2 +- .../snowflake/client/jdbc/SnowflakeType.java | 2 +- .../snowflake/client/jdbc/SnowflakeUtil.java | 2 +- .../client/jdbc/MockConnectionTest.java | 3 +- .../snowflake/client/jdbc/ResultSetIT.java | 11 +- 12 files changed, 185 insertions(+), 193 deletions(-) diff --git a/src/main/java/net/snowflake/client/core/JsonSqlInput.java b/src/main/java/net/snowflake/client/core/JsonSqlInput.java index f007b0db3..49659dd1e 100644 --- a/src/main/java/net/snowflake/client/core/JsonSqlInput.java +++ b/src/main/java/net/snowflake/client/core/JsonSqlInput.java @@ -1,7 +1,6 @@ package net.snowflake.client.core; import com.fasterxml.jackson.databind.JsonNode; - import java.io.InputStream; import java.io.Reader; import java.math.BigDecimal; @@ -11,146 +10,146 @@ public class JsonSqlInput implements SQLInput { - private final JsonNode input; - private final Iterator elements; - - public JsonSqlInput(JsonNode input) { - this.input = input; - elements = input.elements(); - } - - @Override - public String readString() throws SQLException { - return elements.next().textValue(); - } - - @Override - public boolean readBoolean() throws SQLException { - return elements.next().booleanValue(); - } - - @Override - public byte readByte() throws SQLException { - return 0; - } - - @Override - public short readShort() throws SQLException { - return 0; - } - - @Override - public int readInt() throws SQLException { - return 0; - } - - @Override - public long readLong() throws SQLException { - return 0; - } - - @Override - public float readFloat() throws SQLException { - return 0; - } - - @Override - public double readDouble() throws SQLException { - return 0; - } - - @Override - public BigDecimal readBigDecimal() throws SQLException { - return null; - } - - @Override - public byte[] readBytes() throws SQLException { - return new byte[0]; - } - - @Override - public Date readDate() throws SQLException { - return null; - } - - @Override - public Time readTime() throws SQLException { - return null; - } - - @Override - public Timestamp readTimestamp() throws SQLException { - return null; - } - - @Override - public Reader readCharacterStream() throws SQLException { - return null; - } - - @Override - public InputStream readAsciiStream() throws SQLException { - return null; - } - - @Override - public InputStream readBinaryStream() throws SQLException { - return null; - } - - @Override - public Object readObject() throws SQLException { - return null; - } - - @Override - public Ref readRef() throws SQLException { - return null; - } - - @Override - public Blob readBlob() throws SQLException { - return null; - } - - @Override - public Clob readClob() throws SQLException { - return null; - } - - @Override - public Array readArray() throws SQLException { - return null; - } - - @Override - public boolean wasNull() throws SQLException { - return false; - } - - @Override - public URL readURL() throws SQLException { - return null; - } - - @Override - public NClob readNClob() throws SQLException { - return null; - } - - @Override - public String readNString() throws SQLException { - return null; - } - - @Override - public SQLXML readSQLXML() throws SQLException { - return null; - } - - @Override - public RowId readRowId() throws SQLException { - return null; - } + private final JsonNode input; + private final Iterator elements; + + public JsonSqlInput(JsonNode input) { + this.input = input; + elements = input.elements(); + } + + @Override + public String readString() throws SQLException { + return elements.next().textValue(); + } + + @Override + public boolean readBoolean() throws SQLException { + return elements.next().booleanValue(); + } + + @Override + public byte readByte() throws SQLException { + return 0; + } + + @Override + public short readShort() throws SQLException { + return 0; + } + + @Override + public int readInt() throws SQLException { + return 0; + } + + @Override + public long readLong() throws SQLException { + return 0; + } + + @Override + public float readFloat() throws SQLException { + return 0; + } + + @Override + public double readDouble() throws SQLException { + return 0; + } + + @Override + public BigDecimal readBigDecimal() throws SQLException { + return null; + } + + @Override + public byte[] readBytes() throws SQLException { + return new byte[0]; + } + + @Override + public Date readDate() throws SQLException { + return null; + } + + @Override + public Time readTime() throws SQLException { + return null; + } + + @Override + public Timestamp readTimestamp() throws SQLException { + return null; + } + + @Override + public Reader readCharacterStream() throws SQLException { + return null; + } + + @Override + public InputStream readAsciiStream() throws SQLException { + return null; + } + + @Override + public InputStream readBinaryStream() throws SQLException { + return null; + } + + @Override + public Object readObject() throws SQLException { + return null; + } + + @Override + public Ref readRef() throws SQLException { + return null; + } + + @Override + public Blob readBlob() throws SQLException { + return null; + } + + @Override + public Clob readClob() throws SQLException { + return null; + } + + @Override + public Array readArray() throws SQLException { + return null; + } + + @Override + public boolean wasNull() throws SQLException { + return false; + } + + @Override + public URL readURL() throws SQLException { + return null; + } + + @Override + public NClob readNClob() throws SQLException { + return null; + } + + @Override + public String readNString() throws SQLException { + return null; + } + + @Override + public SQLXML readSQLXML() throws SQLException { + return null; + } + + @Override + public RowId readRowId() throws SQLException { + return null; + } } diff --git a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java index 78ecd4649..c66705c63 100644 --- a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java @@ -6,16 +6,15 @@ import static net.snowflake.client.core.StmtUtil.eventHandler; import static net.snowflake.client.jdbc.SnowflakeUtil.systemGetProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import java.io.ByteArrayInputStream; import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; import java.sql.*; import java.util.TimeZone; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import net.snowflake.client.core.arrow.ArrowVectorConverter; import net.snowflake.client.jdbc.*; import net.snowflake.client.jdbc.ArrowResultChunk.ArrowChunkIterator; diff --git a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java index ca8bb6d5b..0244ab2a9 100644 --- a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java @@ -4,16 +4,14 @@ package net.snowflake.client.core; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import java.math.BigDecimal; import java.math.RoundingMode; import java.nio.ByteBuffer; import java.sql.*; import java.util.TimeZone; - -import com.amazonaws.jmespath.ObjectMapperSingleton; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import net.snowflake.client.core.arrow.ArrowResultUtil; import net.snowflake.client.jdbc.*; import net.snowflake.client.log.ArgSupplier; @@ -25,12 +23,11 @@ import net.snowflake.common.core.SFTimestamp; import org.apache.arrow.vector.Float8Vector; -import javax.sql.rowset.serial.SQLInputImpl; - /** 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 = new ObjectMapper(); // TODO structuredType is it proper init? + private static final ObjectMapper OBJECT_MAPPER = + new ObjectMapper(); // TODO structuredType is it proper init? TimeZone sessionTimeZone; @@ -49,23 +46,23 @@ public abstract class SFJsonResultSet extends SFBaseResultSet { */ protected abstract Object getObjectInternal(int columnIndex) throws SFException; -// public getObject(int columnIndex) throws SFException { -// // check metaData -// int type = resultSetMetaData.getColumnType(columnIndex); -// -//// if (typeCanBeConvertToT) -// -// T t = new T(); -// t.readSQL(new SQLInput()); -// -// -// -// } + // public getObject(int columnIndex) throws SFException { + // // check metaData + // int type = resultSetMetaData.getColumnType(columnIndex); + // + //// if (typeCanBeConvertToT) + // + // T t = new T(); + // t.readSQL(new SQLInput()); + // + // + // + // } public Object getObject(int columnIndex) throws SFException { int type = resultSetMetaData.getColumnType(columnIndex); - Object obj = getObjectInternal(columnIndex); //JsonNode + Object obj = getObjectInternal(columnIndex); // JsonNode if (obj == null) { return null; } diff --git a/src/main/java/net/snowflake/client/core/SFResultSet.java b/src/main/java/net/snowflake/client/core/SFResultSet.java index 20fcc095d..8dbe70066 100644 --- a/src/main/java/net/snowflake/client/core/SFResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFResultSet.java @@ -310,7 +310,8 @@ protected Object getObjectInternal(int columnIndex) throws SFException { retValue = firstChunkSortedRowSet[currentChunkRowIndex][internalColumnIndex]; } else if (firstChunkRowset != null) { retValue = - JsonResultChunk.extractCell(firstChunkRowset, currentChunkRowIndex, internalColumnIndex, resultSetMetaData); + JsonResultChunk.extractCell( + firstChunkRowset, currentChunkRowIndex, internalColumnIndex, resultSetMetaData); } else if (currentChunk != null) { // StructuredType should return JsonNode too retValue = currentChunk.getCell(currentChunkRowIndex, internalColumnIndex); @@ -319,7 +320,7 @@ protected Object getObjectInternal(int columnIndex) throws SFException { } wasNull = retValue == null; - //TODO structuredType must be JsonNode instance + // TODO structuredType must be JsonNode instance return retValue; } diff --git a/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java b/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java index a208586ab..c17aa4961 100644 --- a/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java +++ b/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java @@ -4,20 +4,14 @@ package net.snowflake.client.jdbc; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import java.lang.ref.SoftReference; import java.nio.charset.StandardCharsets; -import java.sql.Types; import java.util.ArrayList; import java.util.BitSet; import java.util.LinkedList; import java.util.List; - -import com.fasterxml.jackson.databind.ObjectMapper; -import net.snowflake.client.core.ObjectMapperFactory; import net.snowflake.client.core.SFBaseSession; -import net.snowflake.client.core.SFException; import net.snowflake.client.core.SFResultSetMetaData; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; @@ -39,7 +33,8 @@ public JsonResultChunk( this.session = session; } - public static Object extractCell(JsonNode resultData, int rowIdx, int colIdx, SFResultSetMetaData resultSetMetaData) { + public static Object extractCell( + JsonNode resultData, int rowIdx, int colIdx, SFResultSetMetaData resultSetMetaData) { JsonNode currentRow = resultData.get(rowIdx); JsonNode colNode = currentRow.get(colIdx); @@ -50,7 +45,7 @@ public static Object extractCell(JsonNode resultData, int rowIdx, int colIdx, SF return colNode.numberValue(); } // TODO: structuredType - need response with json but not as String - else if (colNode.isObject()) { + else if (colNode.isObject()) { return colNode; } else if (colNode.isNull()) { return null; diff --git a/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java b/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java index b60a4b4fc..133402e7d 100644 --- a/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java +++ b/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java @@ -286,7 +286,8 @@ public ResultSetMetaData getMetaData() throws SQLException { public Object getObject(int columnIndex) throws SQLException { raiseSQLExceptionIfResultSetIsClosed(); - return resultSetForNext.getObject(columnIndex); // TODO structuredType does it work out of the box? + return resultSetForNext.getObject( + columnIndex); // TODO structuredType does it work out of the box? } public BigDecimal getBigDecimal(int columnIndex) throws SQLException { diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java index e9d246c8b..e6e6fc8bc 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java @@ -19,8 +19,6 @@ import net.snowflake.client.log.SFLoggerFactory; import net.snowflake.common.core.SqlState; -import javax.sql.rowset.serial.SQLInputImpl; - /** Base class for query result set and metadata result set */ public abstract class SnowflakeBaseResultSet implements ResultSet { static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeBaseResultSet.class); @@ -1327,8 +1325,8 @@ public T getObject(int columnIndex, Class type) throws SQLException { SQLInput sqlInput = (SQLInput) getObject(columnIndex); instance.readSQL(sqlInput, null); return (T) instance; - } //TODO structuredType clean exceptions - catch (Exception e) { + } // TODO structuredType clean exceptions + catch (Exception e) { throw new RuntimeException(e); } } else { @@ -1340,7 +1338,7 @@ public T getObject(int columnIndex, Class type) throws SQLException { public T getObject(String columnLabel, Class type) throws SQLException { logger.debug("public T getObject(String columnLabel,Class type)", false); return getObject(findColumn(columnLabel), type); -// throw new SnowflakeLoggedFeatureNotSupportedException(session); + // throw new SnowflakeLoggedFeatureNotSupportedException(session); } @SuppressWarnings("unchecked") diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java index 733b46a22..3d2c6888b 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java @@ -106,7 +106,7 @@ public boolean isCaseSensitive(int column) throws SQLException { int colType = getColumnType(column); switch (colType) { - // TODO structuredType how about other types? + // TODO structuredType how about other types? // Note: SF types ARRAY, OBJECT, GEOMETRY are also represented as VARCHAR. case Types.VARCHAR: case Types.CHAR: diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java index 8bd1e1b58..7134cb4c8 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java @@ -48,7 +48,7 @@ public static SnowflakeType fromString(String name) { } public static JavaDataType getJavaType(SnowflakeType type) { - //TODO structuredType + // TODO structuredType 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 703032a12..7b255cc08 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java @@ -224,7 +224,7 @@ public static SnowflakeColumnMetadata extractColumnMetadata( case OBJECT: colType = Types.STRUCT; - //TODO : structuredType + // TODO : structuredType extColTypeName = "STRUCT"; break; diff --git a/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java b/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java index 887b3390c..441f1b974 100644 --- a/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java +++ b/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java @@ -619,7 +619,8 @@ public boolean next() { @Override protected Object getObjectInternal(int columnIndex) { - return JsonResultChunk.extractCell(resultJson, currentRowIdx, columnIndex - 1, resultSetMetaData); + return JsonResultChunk.extractCell( + resultJson, currentRowIdx, columnIndex - 1, resultSetMetaData); } @Override diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java index 5208f8540..e5d9e05b5 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java @@ -49,9 +49,10 @@ public void testFindColumn() throws SQLException { connection.close(); } - public static class TestClass implements SQLData{ + public static class TestClass implements SQLData { private String x; + @Override public String getSQLTypeName() throws SQLException { return null; @@ -63,9 +64,7 @@ public void readSQL(SQLInput stream, String typeName) throws SQLException { } @Override - public void writeSQL(SQLOutput stream) throws SQLException { - - } + public void writeSQL(SQLOutput stream) throws SQLException {} } @Test @@ -86,7 +85,9 @@ public void testMapJsonFromChunks() throws SQLException { Connection connection = init(); Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery("select {'x':'a'}::OBJECT(x VARCHAR) FROM TABLE(GENERATOR(ROWCOUNT=>30000))"); + ResultSet resultSet = + statement.executeQuery( + "select {'x':'a'}::OBJECT(x VARCHAR) FROM TABLE(GENERATOR(ROWCOUNT=>30000))"); int i = 0; while (resultSet.next()) { System.out.println(i++); From 750cee276cc363a1e8b918468e1ee6baf23f348c Mon Sep 17 00:00:00 2001 From: Piotr Fus Date: Wed, 15 Nov 2023 09:25:47 +0100 Subject: [PATCH 09/14] Fix TODOs, introduce factories --- .../client/core/SFArrowResultSet.java | 5 ++- .../client/core/SFJsonResultSet.java | 22 ++++-------- .../snowflake/client/core/SFResultSet.java | 9 ++--- .../structs/SnowflakeObjectTypeFactories.java | 29 +++++++++++++++ .../client/jdbc/JsonResultChunk.java | 12 ++----- .../client/jdbc/SFAsyncResultSet.java | 3 +- .../client/jdbc/SnowflakeBaseResultSet.java | 36 +++++++++++-------- .../jdbc/SnowflakeResultSetMetaDataV1.java | 2 +- .../snowflake/client/jdbc/SnowflakeType.java | 5 ++- .../snowflake/client/jdbc/SnowflakeUtil.java | 4 +-- .../net/snowflake/client/util/Predicates.java | 9 +++++ .../client/jdbc/MockConnectionTest.java | 3 +- .../client/jdbc/ResultSetAsyncIT.java | 1 + .../snowflake/client/jdbc/ResultSetIT.java | 19 ++++++++-- 14 files changed, 98 insertions(+), 61 deletions(-) create mode 100644 src/main/java/net/snowflake/client/core/structs/SnowflakeObjectTypeFactories.java create mode 100644 src/main/java/net/snowflake/client/util/Predicates.java diff --git a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java index c66705c63..53d09a09d 100644 --- a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java @@ -479,7 +479,10 @@ public Object getObject(int columnIndex) throws SFException { converter.setUseSessionTimezone(useSessionTimezone); converter.setSessionTimeZone(timeZone); Object obj = converter.toObject(index); - // TODO structuredType clean this up + return handleObjectType(columnIndex, obj); + } + + private Object handleObjectType(int columnIndex, Object obj) throws SFException { if (resultSetMetaData.getColumnType(columnIndex) == Types.STRUCT) { try { JsonNode jsonNode = OBJECT_MAPPER.readTree((String) obj); diff --git a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java index 0244ab2a9..af3626502 100644 --- a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java @@ -10,7 +10,10 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.nio.ByteBuffer; -import java.sql.*; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; import java.util.TimeZone; import net.snowflake.client.core.arrow.ArrowResultUtil; import net.snowflake.client.jdbc.*; @@ -26,8 +29,7 @@ /** 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 = - new ObjectMapper(); // TODO structuredType is it proper init? + private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper(); TimeZone sessionTimeZone; @@ -46,23 +48,11 @@ public abstract class SFJsonResultSet extends SFBaseResultSet { */ protected abstract Object getObjectInternal(int columnIndex) throws SFException; - // public getObject(int columnIndex) throws SFException { - // // check metaData - // int type = resultSetMetaData.getColumnType(columnIndex); - // - //// if (typeCanBeConvertToT) - // - // T t = new T(); - // t.readSQL(new SQLInput()); - // - // - // - // } public Object getObject(int columnIndex) throws SFException { int type = resultSetMetaData.getColumnType(columnIndex); - Object obj = getObjectInternal(columnIndex); // JsonNode + Object obj = getObjectInternal(columnIndex); if (obj == null) { return null; } diff --git a/src/main/java/net/snowflake/client/core/SFResultSet.java b/src/main/java/net/snowflake/client/core/SFResultSet.java index 8dbe70066..bfcdb266c 100644 --- a/src/main/java/net/snowflake/client/core/SFResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFResultSet.java @@ -306,21 +306,16 @@ protected Object getObjectInternal(int columnIndex) throws SFException { final int internalColumnIndex = columnIndex - 1; Object retValue; if (sortResult) { - // StructuredType should return JsonNode too retValue = firstChunkSortedRowSet[currentChunkRowIndex][internalColumnIndex]; } else if (firstChunkRowset != null) { retValue = - JsonResultChunk.extractCell( - firstChunkRowset, currentChunkRowIndex, internalColumnIndex, resultSetMetaData); + JsonResultChunk.extractCell(firstChunkRowset, currentChunkRowIndex, internalColumnIndex); } else if (currentChunk != null) { - // StructuredType should return JsonNode too retValue = currentChunk.getCell(currentChunkRowIndex, internalColumnIndex); } else { throw new SFException(ErrorCode.COLUMN_DOES_NOT_EXIST, columnIndex); } wasNull = retValue == null; - - // TODO structuredType must be JsonNode instance return retValue; } @@ -332,7 +327,7 @@ private void sortResultSet() { firstChunkSortedRowSet[rowIdx] = new Object[columnCount]; for (int colIdx = 0; colIdx < columnCount; colIdx++) { firstChunkSortedRowSet[rowIdx][colIdx] = - JsonResultChunk.extractCell(firstChunkRowset, rowIdx, colIdx, resultSetMetaData); + JsonResultChunk.extractCell(firstChunkRowset, rowIdx, colIdx); } } diff --git a/src/main/java/net/snowflake/client/core/structs/SnowflakeObjectTypeFactories.java b/src/main/java/net/snowflake/client/core/structs/SnowflakeObjectTypeFactories.java new file mode 100644 index 000000000..9c200f675 --- /dev/null +++ b/src/main/java/net/snowflake/client/core/structs/SnowflakeObjectTypeFactories.java @@ -0,0 +1,29 @@ +package net.snowflake.client.core.structs; + +import static net.snowflake.client.util.Predicates.notNull; + +import java.sql.SQLData; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + +public class SnowflakeObjectTypeFactories { + private static final Map, Supplier> factories = new HashMap<>(); + + public static synchronized void register(Class type, Supplier factory) { + notNull(type, "type cannot be null"); + notNull(factory, "factory cannot be null"); + factories.put(type, factory); + } + + public static synchronized void unregister(Class type) { + notNull(type, "type cannot be null"); + factories.remove(type); + } + + public static synchronized Optional> get(Class type) { + notNull(type, "type cannot be null"); + return Optional.ofNullable(factories.get(type)); + } +} diff --git a/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java b/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java index c17aa4961..a486fa15e 100644 --- a/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java +++ b/src/main/java/net/snowflake/client/jdbc/JsonResultChunk.java @@ -12,7 +12,6 @@ import java.util.LinkedList; import java.util.List; import net.snowflake.client.core.SFBaseSession; -import net.snowflake.client.core.SFResultSetMetaData; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; import net.snowflake.common.core.SqlState; @@ -33,24 +32,19 @@ public JsonResultChunk( this.session = session; } - public static Object extractCell( - JsonNode resultData, int rowIdx, int colIdx, SFResultSetMetaData resultSetMetaData) { + public static Object extractCell(JsonNode resultData, int rowIdx, int colIdx) { JsonNode currentRow = resultData.get(rowIdx); JsonNode colNode = currentRow.get(colIdx); if (colNode.isTextual()) { - return colNode.textValue(); + return colNode.asText(); } else if (colNode.isNumber()) { return colNode.numberValue(); - } - // TODO: structuredType - need response with json but not as String - else if (colNode.isObject()) { - return colNode; } else if (colNode.isNull()) { return null; } - throw new RuntimeException("Unknown json type"); + throw new RuntimeException("Unknow json type"); } public void tryReuse(ResultChunkDataCache cache) { diff --git a/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java b/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java index 133402e7d..7fe9ecbbe 100644 --- a/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java +++ b/src/main/java/net/snowflake/client/jdbc/SFAsyncResultSet.java @@ -286,8 +286,7 @@ public ResultSetMetaData getMetaData() throws SQLException { public Object getObject(int columnIndex) throws SQLException { raiseSQLExceptionIfResultSetIsClosed(); - return resultSetForNext.getObject( - columnIndex); // TODO structuredType does it work out of the box? + return resultSetForNext.getObject(columnIndex); } public BigDecimal getBigDecimal(int columnIndex) throws SQLException { diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java index e6e6fc8bc..998b9cef9 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java @@ -10,11 +10,11 @@ import java.math.BigDecimal; import java.net.URL; import java.sql.*; -import java.util.Calendar; -import java.util.HashMap; -import java.util.Map; -import java.util.TimeZone; +import java.sql.Date; +import java.util.*; +import java.util.function.Supplier; import net.snowflake.client.core.SFBaseSession; +import net.snowflake.client.core.structs.SnowflakeObjectTypeFactories; import net.snowflake.client.log.SFLogger; import net.snowflake.client.log.SFLoggerFactory; import net.snowflake.common.core.SqlState; @@ -1320,25 +1320,31 @@ public void updateNClob(String columnLabel, Reader reader) throws SQLException { public T getObject(int columnIndex, Class type) throws SQLException { logger.debug("public T getObject(int columnIndex,Class type)", false); if (SQLData.class.isAssignableFrom(type)) { - try { - SQLData instance = (SQLData) type.newInstance(); - SQLInput sqlInput = (SQLInput) getObject(columnIndex); - instance.readSQL(sqlInput, null); - return (T) instance; - } // TODO structuredType clean exceptions - catch (Exception e) { - throw new RuntimeException(e); - } + Optional> typeFactory = SnowflakeObjectTypeFactories.get(type); + SQLData instance = + typeFactory + .map(Supplier::get) + .orElseGet(() -> createUsingReflection((Class) type)); + SQLInput sqlInput = (SQLInput) getObject(columnIndex); + instance.readSQL(sqlInput, null); + return (T) instance; } else { return (T) getObject(columnIndex); } } - // @Override + private SQLData createUsingReflection(Class type) { + try { + return type.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override public T getObject(String columnLabel, Class type) throws SQLException { logger.debug("public T getObject(String columnLabel,Class type)", false); return getObject(findColumn(columnLabel), type); - // throw new SnowflakeLoggedFeatureNotSupportedException(session); } @SuppressWarnings("unchecked") diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java index 3d2c6888b..4ac3a1a14 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java @@ -106,7 +106,7 @@ public boolean isCaseSensitive(int column) throws SQLException { int colType = getColumnType(column); switch (colType) { - // TODO structuredType how about other types? + // TODO structuredType fill for Array and Map // Note: SF types ARRAY, OBJECT, GEOMETRY are also represented as VARCHAR. case Types.VARCHAR: case Types.CHAR: diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java index 7134cb4c8..1226b359f 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java @@ -48,7 +48,7 @@ public static SnowflakeType fromString(String name) { } public static JavaDataType getJavaType(SnowflakeType type) { - // TODO structuredType + // TODO structuredType fill for Array and Map switch (type) { case TEXT: return JavaDataType.JAVA_STRING; @@ -72,11 +72,10 @@ public static JavaDataType getJavaType(SnowflakeType type) { case ARRAY: case VARIANT: return JavaDataType.JAVA_STRING; - case OBJECT: - return JavaDataType.JAVA_STRING; case BINARY: return JavaDataType.JAVA_BYTES; case ANY: + case OBJECT: return JavaDataType.JAVA_OBJECT; default: // Those are not supported, but no reason to panic diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java index 7b255cc08..e3825f132 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java @@ -217,6 +217,7 @@ public static SnowflakeColumnMetadata extractColumnMetadata( extColTypeName = "BOOLEAN"; break; + // TODO structuredType fill for Array and Map case ARRAY: colType = Types.VARCHAR; extColTypeName = "ARRAY"; @@ -224,8 +225,7 @@ public static SnowflakeColumnMetadata extractColumnMetadata( case OBJECT: colType = Types.STRUCT; - // TODO : structuredType - extColTypeName = "STRUCT"; + extColTypeName = "OBJECT"; break; case VARIANT: diff --git a/src/main/java/net/snowflake/client/util/Predicates.java b/src/main/java/net/snowflake/client/util/Predicates.java new file mode 100644 index 000000000..fe5e17e7f --- /dev/null +++ b/src/main/java/net/snowflake/client/util/Predicates.java @@ -0,0 +1,9 @@ +package net.snowflake.client.util; + +public class Predicates { + public static void notNull(Object o, String msg) { + if (o == null) { + throw new NullPointerException(msg); + } + } +} diff --git a/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java b/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java index 441f1b974..49bc6184f 100644 --- a/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java +++ b/src/test/java/net/snowflake/client/jdbc/MockConnectionTest.java @@ -619,8 +619,7 @@ public boolean next() { @Override protected Object getObjectInternal(int columnIndex) { - return JsonResultChunk.extractCell( - resultJson, currentRowIdx, columnIndex - 1, resultSetMetaData); + return JsonResultChunk.extractCell(resultJson, currentRowIdx, columnIndex - 1); } @Override diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetAsyncIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetAsyncIT.java index 2c2b94506..489413cbc 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetAsyncIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetAsyncIT.java @@ -224,6 +224,7 @@ public void testGetMethods() throws Throwable { Clob clob = connection.createClob(); clob.setString(1, "hello world"); Statement statement = connection.createStatement(); + // TODO structureType - add to test when WRITE is ready statement.execute( "create or replace table test_get(colA integer, colB number, colC number, colD string, colE double, colF float, colG boolean, colH text, colI binary(3), colJ number(38,9), colK int, colL date, colM time, colN timestamp_ltz)"); diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java index e5d9e05b5..038bd4513 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java @@ -17,6 +17,7 @@ import net.snowflake.client.ConditionalIgnoreRule; import net.snowflake.client.RunningOnGithubAction; import net.snowflake.client.category.TestCategoryResultSet; +import net.snowflake.client.core.structs.SnowflakeObjectTypeFactories; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -68,8 +69,21 @@ public void writeSQL(SQLOutput stream) throws SQLException {} } @Test - public void testMapJson() throws SQLException { + public void testMapStructToObjectWithFactory() throws SQLException { + testMapJson(true); + } + @Test + public void testMapStructToObjectWithReflection() throws SQLException { + testMapJson(false); + } + + private void testMapJson(boolean registerFactory) throws SQLException { + if (registerFactory) { + SnowflakeObjectTypeFactories.register(TestClass.class, TestClass::new); + } else { + SnowflakeObjectTypeFactories.unregister(TestClass.class); + } Connection connection = init(); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("select {'x':'a'}::OBJECT(x VARCHAR)"); @@ -81,7 +95,7 @@ public void testMapJson() throws SQLException { } @Test - public void testMapJsonFromChunks() throws SQLException { + public void testMapStructsFromChunks() throws SQLException { Connection connection = init(); Statement statement = connection.createStatement(); @@ -90,7 +104,6 @@ public void testMapJsonFromChunks() throws SQLException { "select {'x':'a'}::OBJECT(x VARCHAR) FROM TABLE(GENERATOR(ROWCOUNT=>30000))"); int i = 0; while (resultSet.next()) { - System.out.println(i++); TestClass object = resultSet.getObject(1, TestClass.class); assertEquals("a", object.x); } From a2717e7bf259e5292358a14928bad5cd8cb509c6 Mon Sep 17 00:00:00 2001 From: Piotr Fus Date: Thu, 16 Nov 2023 11:50:27 +0100 Subject: [PATCH 10/14] Fix after review --- .../client/core/SFArrowResultSet.java | 21 ++++++++++--------- .../client/core/SFJsonResultSet.java | 21 ++++++++++--------- .../structs/SnowflakeObjectTypeFactories.java | 21 +++++++++---------- .../net/snowflake/client/jdbc/ErrorCode.java | 3 ++- .../net/snowflake/client/util/Predicates.java | 9 -------- 5 files changed, 34 insertions(+), 41 deletions(-) delete mode 100644 src/main/java/net/snowflake/client/util/Predicates.java diff --git a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java index 53d09a09d..554fbaf8e 100644 --- a/src/main/java/net/snowflake/client/core/SFArrowResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFArrowResultSet.java @@ -3,18 +3,9 @@ */ package net.snowflake.client.core; -import static net.snowflake.client.core.StmtUtil.eventHandler; -import static net.snowflake.client.jdbc.SnowflakeUtil.systemGetProperty; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.sql.*; -import java.util.TimeZone; import net.snowflake.client.core.arrow.ArrowVectorConverter; import net.snowflake.client.jdbc.*; import net.snowflake.client.jdbc.ArrowResultChunk.ArrowChunkIterator; @@ -29,6 +20,16 @@ import net.snowflake.common.core.SqlState; import org.apache.arrow.memory.RootAllocator; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.*; +import java.util.TimeZone; + +import static net.snowflake.client.core.StmtUtil.eventHandler; +import static net.snowflake.client.jdbc.SnowflakeUtil.systemGetProperty; + /** Arrow result set implementation */ public class SFArrowResultSet extends SFBaseResultSet implements DataConversionContext { private static final SFLogger logger = SFLoggerFactory.getLogger(SFArrowResultSet.class); @@ -488,7 +489,7 @@ private Object handleObjectType(int columnIndex, Object obj) throws SFException JsonNode jsonNode = OBJECT_MAPPER.readTree((String) obj); return new JsonSqlInput(jsonNode); } catch (JsonProcessingException e) { - throw new RuntimeException(e); + throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); } } return obj; diff --git a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java index af3626502..cd7d76d7a 100644 --- a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java @@ -7,14 +7,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.nio.ByteBuffer; -import java.sql.Date; -import java.sql.Time; -import java.sql.Timestamp; -import java.sql.Types; -import java.util.TimeZone; import net.snowflake.client.core.arrow.ArrowResultUtil; import net.snowflake.client.jdbc.*; import net.snowflake.client.log.ArgSupplier; @@ -26,6 +18,15 @@ import net.snowflake.common.core.SFTimestamp; import org.apache.arrow.vector.Float8Vector; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.nio.ByteBuffer; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.TimeZone; + /** 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); @@ -98,12 +99,12 @@ public Object getObject(int columnIndex) throws SFException { } } - private Object getSqlInput(String input) { + private Object getSqlInput(String input) throws SFException { try { JsonNode jsonNode = OBJECT_MAPPER.readTree(input); return new JsonSqlInput(jsonNode); } catch (JsonProcessingException e) { - throw new RuntimeException(e); + throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); } } diff --git a/src/main/java/net/snowflake/client/core/structs/SnowflakeObjectTypeFactories.java b/src/main/java/net/snowflake/client/core/structs/SnowflakeObjectTypeFactories.java index 9c200f675..4548ae45b 100644 --- a/src/main/java/net/snowflake/client/core/structs/SnowflakeObjectTypeFactories.java +++ b/src/main/java/net/snowflake/client/core/structs/SnowflakeObjectTypeFactories.java @@ -1,29 +1,28 @@ package net.snowflake.client.core.structs; -import static net.snowflake.client.util.Predicates.notNull; - import java.sql.SQLData; -import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; public class SnowflakeObjectTypeFactories { - private static final Map, Supplier> factories = new HashMap<>(); + private static final Map, Supplier> factories = new ConcurrentHashMap<>(); - public static synchronized void register(Class type, Supplier factory) { - notNull(type, "type cannot be null"); - notNull(factory, "factory cannot be null"); + public static void register(Class type, Supplier factory) { + Objects.requireNonNull((Object) type, "type cannot be null"); + Objects.requireNonNull((Object) factory, "factory cannot be null"); factories.put(type, factory); } - public static synchronized void unregister(Class type) { - notNull(type, "type cannot be null"); + public static void unregister(Class type) { + Objects.requireNonNull((Object) type, "type cannot be null"); factories.remove(type); } - public static synchronized Optional> get(Class type) { - notNull(type, "type cannot be null"); + public static Optional> get(Class type) { + Objects.requireNonNull((Object) type, "type cannot be null"); return Optional.ofNullable(factories.get(type)); } } diff --git a/src/main/java/net/snowflake/client/jdbc/ErrorCode.java b/src/main/java/net/snowflake/client/jdbc/ErrorCode.java index e0cb6d414..b9cc71491 100644 --- a/src/main/java/net/snowflake/client/jdbc/ErrorCode.java +++ b/src/main/java/net/snowflake/client/jdbc/ErrorCode.java @@ -82,7 +82,8 @@ public enum ErrorCode { INVALID_CONNECT_STRING(200059, SqlState.CONNECTION_EXCEPTION), INVALID_OKTA_USERNAME(200060, SqlState.CONNECTION_EXCEPTION), GCP_SERVICE_ERROR(200061, SqlState.SYSTEM_ERROR), - AUTHENTICATOR_REQUEST_TIMEOUT(200062, SqlState.CONNECTION_EXCEPTION); + AUTHENTICATOR_REQUEST_TIMEOUT(200062, SqlState.CONNECTION_EXCEPTION), + INVALID_STRUCT_DATA(200063, SqlState.DATA_EXCEPTION); public static final String errorMessageResource = "net.snowflake.client.jdbc.jdbc_error_messages"; diff --git a/src/main/java/net/snowflake/client/util/Predicates.java b/src/main/java/net/snowflake/client/util/Predicates.java deleted file mode 100644 index fe5e17e7f..000000000 --- a/src/main/java/net/snowflake/client/util/Predicates.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.snowflake.client.util; - -public class Predicates { - public static void notNull(Object o, String msg) { - if (o == null) { - throw new NullPointerException(msg); - } - } -} From 779430af33c71e80615c64241ee85edbf6d94db7 Mon Sep 17 00:00:00 2001 From: Przemyslaw Motacki Date: Fri, 17 Nov 2023 05:37:23 +0100 Subject: [PATCH 11/14] StructuredType simple implementations for List and Map # Conflicts: # src/main/java/net/snowflake/client/core/SFJsonResultSet.java --- .../snowflake/client/core/JsonSqlInput.java | 5 ++ .../client/core/SFJsonResultSet.java | 27 ++++++++++ .../client/jdbc/SnowflakeBaseResultSet.java | 51 ++++++++++++++++++- .../snowflake/client/jdbc/SnowflakeUtil.java | 2 +- .../snowflake/client/AbstractDriverIT.java | 4 ++ .../snowflake/client/jdbc/ResultSetIT.java | 42 ++++++++++++++- 6 files changed, 128 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/snowflake/client/core/JsonSqlInput.java b/src/main/java/net/snowflake/client/core/JsonSqlInput.java index 49659dd1e..e2238699e 100644 --- a/src/main/java/net/snowflake/client/core/JsonSqlInput.java +++ b/src/main/java/net/snowflake/client/core/JsonSqlInput.java @@ -10,6 +10,11 @@ public class JsonSqlInput implements SQLInput { +// TODO extractedType maybe getObject should return raw input then it won't be needed + public JsonNode getInput() { + return input; + } + private final JsonNode input; private final Iterator elements; diff --git a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java index cd7d76d7a..3d6cc9ade 100644 --- a/src/main/java/net/snowflake/client/core/SFJsonResultSet.java +++ b/src/main/java/net/snowflake/client/core/SFJsonResultSet.java @@ -7,6 +7,16 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.nio.ByteBuffer; +import java.sql.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.TimeZone; + +import com.fasterxml.jackson.databind.node.ArrayNode; import net.snowflake.client.core.arrow.ArrowResultUtil; import net.snowflake.client.jdbc.*; import net.snowflake.client.log.ArgSupplier; @@ -94,11 +104,28 @@ public Object getObject(int columnIndex) throws SFException { case Types.STRUCT: return getSqlInput((String) obj); + case Types.ARRAY: + return getArrayOfSqlInput((String) obj); + default: throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, "data type: " + type); } } + private Object getArrayOfSqlInput(String input) throws SFException { + try { + List result = new ArrayList<>(); + ArrayNode arrayNode = (ArrayNode) OBJECT_MAPPER.readTree(input); + Iterator nodeElements = arrayNode.elements(); + while (nodeElements.hasNext()) { + result.add(new JsonSqlInput((JsonNode) nodeElements.next())); + } + return result; + } catch (JsonProcessingException e) { + throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA); + } + } + private Object getSqlInput(String input) throws SFException { try { JsonNode jsonNode = OBJECT_MAPPER.readTree(input); diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java index 998b9cef9..106dd6f1d 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java @@ -13,6 +13,12 @@ import java.sql.Date; import java.util.*; import java.util.function.Supplier; +import java.util.stream.Collectors; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import net.snowflake.client.core.JsonSqlInput; +import net.snowflake.client.core.ObjectMapperFactory; import net.snowflake.client.core.SFBaseSession; import net.snowflake.client.core.structs.SnowflakeObjectTypeFactories; import net.snowflake.client.log.SFLogger; @@ -32,6 +38,7 @@ public abstract class SnowflakeBaseResultSet implements ResultSet { protected Map parameters = new HashMap<>(); private int fetchSize = 0; protected SFBaseSession session = null; + private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper(); SnowflakeBaseResultSet(Statement statement) throws SQLException { this.statement = statement; @@ -1319,7 +1326,7 @@ public void updateNClob(String columnLabel, Reader reader) throws SQLException { // @Override public T getObject(int columnIndex, Class type) throws SQLException { logger.debug("public T getObject(int columnIndex,Class type)", false); - if (SQLData.class.isAssignableFrom(type)) { + if (SQLData.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)) { Optional> typeFactory = SnowflakeObjectTypeFactories.get(type); SQLData instance = typeFactory @@ -1333,6 +1340,48 @@ public T getObject(int columnIndex, Class type) throws SQLException { } } + public List getList(int columnIndex, Class type) throws SQLException { + Optional> typeFactory = SnowflakeObjectTypeFactories.get(type); + List sqlInputs = (List) getObject(columnIndex); + return sqlInputs.stream() + .map(i -> { + SQLData instance = typeFactory + .map(Supplier::get) + .orElseGet(() -> createUsingReflection((Class) type)); + try { + instance.readSQL(i, null); + } catch (SQLException e) { + throw new RuntimeException(e); + } + return (T) instance; + }).collect(Collectors.toList()); + } + + public Map getMap(int columnIndex, Class type) throws SQLException { + Optional> typeFactory = SnowflakeObjectTypeFactories.get(type); +// TODO: structuredType how to get raw json object not as SqlInput + JsonNode jsonNode = ((JsonSqlInput) getObject(columnIndex)).getInput(); + Map map = OBJECT_MAPPER.convertValue(jsonNode, new TypeReference>() { + }); + Map collect = map.entrySet().stream().map(e -> { + SQLData instance = typeFactory + .map(Supplier::get) + .orElseGet(() -> createUsingReflection((Class) type)); + try { + SQLInput sqlInput = new JsonSqlInput(jsonNode.get(e.getKey())); + instance.readSQL(sqlInput, null); + } catch (SQLException ex) { + throw new RuntimeException(ex); + } + return new AbstractMap.SimpleEntry<>(e.getKey(), (T) instance); + }) + .collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue + )); + return collect; + } + private SQLData createUsingReflection(Class type) { try { return type.newInstance(); diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java index e3825f132..7aa7712c4 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeUtil.java @@ -219,7 +219,7 @@ public static SnowflakeColumnMetadata extractColumnMetadata( // TODO structuredType fill for Array and Map case ARRAY: - colType = Types.VARCHAR; + colType = Types.ARRAY; extColTypeName = "ARRAY"; break; diff --git a/src/test/java/net/snowflake/client/AbstractDriverIT.java b/src/test/java/net/snowflake/client/AbstractDriverIT.java index 7ec6243b2..95fd04549 100644 --- a/src/test/java/net/snowflake/client/AbstractDriverIT.java +++ b/src/test/java/net/snowflake/client/AbstractDriverIT.java @@ -308,6 +308,10 @@ public static Connection getConnection( properties.put("role", params.get("role")); properties.put("account", params.get("account")); } +// properties.put("useProxy", "true"); +// properties.put("proxyHost", "localhost"); +// properties.put("proxyPort", "8080"); + properties.put("db", params.get("database")); properties.put("schema", params.get("schema")); properties.put("warehouse", params.get("warehouse")); diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java index 038bd4513..a0a12ce45 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java @@ -13,7 +13,8 @@ import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.sql.*; -import java.util.Properties; +import java.util.*; + import net.snowflake.client.ConditionalIgnoreRule; import net.snowflake.client.RunningOnGithubAction; import net.snowflake.client.category.TestCategoryResultSet; @@ -75,7 +76,12 @@ public void testMapStructToObjectWithFactory() throws SQLException { @Test public void testMapStructToObjectWithReflection() throws SQLException { + testMapJson(true); testMapJson(false); + testMapArray(true); + testMapArray(false); + testReturnAsMap(true); + testReturnAsMap(false); } private void testMapJson(boolean registerFactory) throws SQLException { @@ -93,6 +99,40 @@ private void testMapJson(boolean registerFactory) throws SQLException { statement.close(); connection.close(); } + private void testMapArray(boolean registerFactory) throws SQLException { + if (registerFactory) { + SnowflakeObjectTypeFactories.register(TestClass.class, TestClass::new); + } else { + SnowflakeObjectTypeFactories.unregister(TestClass.class); + } + Connection connection = init(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("select [{'x':'aaa'},{'x': 'bbb'}]::ARRAY(OBJECT(x varchar))"); + resultSet.next(); + List objects = resultSet.unwrap(SnowflakeBaseResultSet.class).getList(1, TestClass.class); + assertEquals(objects.get(0).x, "aaa"); + assertEquals(objects.get(1).x, "bbb"); + statement.close(); + connection.close(); + } + + private void testReturnAsMap(boolean registerFactory) throws SQLException { + if (registerFactory) { + SnowflakeObjectTypeFactories.register(TestClass.class, TestClass::new); + } else { + SnowflakeObjectTypeFactories.unregister(TestClass.class); + } + Connection connection = init(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("select {'x':{'x':'one'},'y':{'x':'two'},'z':{'x':'three'}}::MAP(VARCHAR, OBJECT(x VARCHAR));"); + resultSet.next(); + Map map = resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, TestClass.class); + assertEquals(map.get("x").x, "one"); + assertEquals(map.get("y").x, "two"); + assertEquals(map.get("z").x, "three"); + statement.close(); + connection.close(); + } @Test public void testMapStructsFromChunks() throws SQLException { From 1b01f3b146ff0e1a8d41b1c6ee911ca1a8bfdda6 Mon Sep 17 00:00:00 2001 From: Przemyslaw Motacki Date: Fri, 17 Nov 2023 05:30:04 +0100 Subject: [PATCH 12/14] StructuredType move tests to *LatestIT --- .../client/jdbc/SnowflakeBaseResultSet.java | 21 ++++ .../jdbc/SnowflakeResultSetMetaDataV1.java | 4 +- .../snowflake/client/jdbc/ResultSetIT.java | 100 ---------------- .../client/jdbc/ResultSetLatestIT.java | 107 ++++++++++++++++++ 4 files changed, 131 insertions(+), 101 deletions(-) diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java index 106dd6f1d..315111580 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeBaseResultSet.java @@ -12,6 +12,7 @@ import java.sql.*; import java.sql.Date; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import java.util.stream.Collectors; import com.fasterxml.jackson.core.type.TypeReference; @@ -1357,6 +1358,26 @@ public List getList(int columnIndex, Class type) throws SQLException { }).collect(Collectors.toList()); } + public T[] getArray(int columnIndex, Class type) throws SQLException { + Optional> typeFactory = SnowflakeObjectTypeFactories.get(type); + List sqlInputs = (List) getObject(columnIndex); + T[] arr = (T[]) java.lang.reflect.Array.newInstance(type, sqlInputs.size()); + AtomicInteger counter = new AtomicInteger(0); + sqlInputs.stream() + .forEach(i -> { + SQLData instance = typeFactory + .map(Supplier::get) + .orElseGet(() -> createUsingReflection((Class) type)); + try { + instance.readSQL(i, null); + } catch (SQLException e) { + throw new RuntimeException(e); + } + arr[counter.getAndIncrement()] = (T) instance; + }); + return arr; + } + public Map getMap(int columnIndex, Class type) throws SQLException { Optional> typeFactory = SnowflakeObjectTypeFactories.get(type); // TODO: structuredType how to get raw json object not as SqlInput diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java index 4ac3a1a14..6796c07a8 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeResultSetMetaDataV1.java @@ -106,11 +106,13 @@ public boolean isCaseSensitive(int column) throws SQLException { int colType = getColumnType(column); switch (colType) { - // TODO structuredType fill for Array and Map + // Note: SF types ARRAY, OBJECT, GEOMETRY are also represented as VARCHAR. case Types.VARCHAR: case Types.CHAR: case Types.STRUCT: + // TODO structuredType confirm that ARRAY is case sensitive + case Types.ARRAY: return true; case Types.INTEGER: diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java index a0a12ce45..dd4240a4d 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java @@ -51,106 +51,6 @@ public void testFindColumn() throws SQLException { connection.close(); } - public static class TestClass implements SQLData { - - private String x; - - @Override - public String getSQLTypeName() throws SQLException { - return null; - } - - @Override - public void readSQL(SQLInput stream, String typeName) throws SQLException { - x = stream.readString(); - } - - @Override - public void writeSQL(SQLOutput stream) throws SQLException {} - } - - @Test - public void testMapStructToObjectWithFactory() throws SQLException { - testMapJson(true); - } - - @Test - public void testMapStructToObjectWithReflection() throws SQLException { - testMapJson(true); - testMapJson(false); - testMapArray(true); - testMapArray(false); - testReturnAsMap(true); - testReturnAsMap(false); - } - - private void testMapJson(boolean registerFactory) throws SQLException { - if (registerFactory) { - SnowflakeObjectTypeFactories.register(TestClass.class, TestClass::new); - } else { - SnowflakeObjectTypeFactories.unregister(TestClass.class); - } - Connection connection = init(); - Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery("select {'x':'a'}::OBJECT(x VARCHAR)"); - resultSet.next(); - TestClass object = resultSet.getObject(1, TestClass.class); - assertEquals("a", object.x); - statement.close(); - connection.close(); - } - private void testMapArray(boolean registerFactory) throws SQLException { - if (registerFactory) { - SnowflakeObjectTypeFactories.register(TestClass.class, TestClass::new); - } else { - SnowflakeObjectTypeFactories.unregister(TestClass.class); - } - Connection connection = init(); - Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery("select [{'x':'aaa'},{'x': 'bbb'}]::ARRAY(OBJECT(x varchar))"); - resultSet.next(); - List objects = resultSet.unwrap(SnowflakeBaseResultSet.class).getList(1, TestClass.class); - assertEquals(objects.get(0).x, "aaa"); - assertEquals(objects.get(1).x, "bbb"); - statement.close(); - connection.close(); - } - - private void testReturnAsMap(boolean registerFactory) throws SQLException { - if (registerFactory) { - SnowflakeObjectTypeFactories.register(TestClass.class, TestClass::new); - } else { - SnowflakeObjectTypeFactories.unregister(TestClass.class); - } - Connection connection = init(); - Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery("select {'x':{'x':'one'},'y':{'x':'two'},'z':{'x':'three'}}::MAP(VARCHAR, OBJECT(x VARCHAR));"); - resultSet.next(); - Map map = resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, TestClass.class); - assertEquals(map.get("x").x, "one"); - assertEquals(map.get("y").x, "two"); - assertEquals(map.get("z").x, "three"); - statement.close(); - connection.close(); - } - - @Test - public void testMapStructsFromChunks() throws SQLException { - - Connection connection = init(); - Statement statement = connection.createStatement(); - ResultSet resultSet = - statement.executeQuery( - "select {'x':'a'}::OBJECT(x VARCHAR) FROM TABLE(GENERATOR(ROWCOUNT=>30000))"); - int i = 0; - while (resultSet.next()) { - TestClass object = resultSet.getObject(1, TestClass.class); - assertEquals("a", object.x); - } - statement.close(); - connection.close(); - } - @Test public void testGetColumnClassNameForBinary() throws Throwable { Connection connection = init(); diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetLatestIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetLatestIT.java index 98e7befb5..27f0699e5 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetLatestIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetLatestIT.java @@ -21,6 +21,7 @@ import net.snowflake.client.category.TestCategoryResultSet; import net.snowflake.client.core.SFBaseSession; import net.snowflake.client.core.SessionUtil; +import net.snowflake.client.core.structs.SnowflakeObjectTypeFactories; import net.snowflake.client.jdbc.telemetry.*; import net.snowflake.common.core.SFBinary; import org.apache.arrow.vector.Float8Vector; @@ -927,4 +928,110 @@ public void testGranularTimeFunctionsInUTC() throws SQLException { connection.close(); } } + + public static class TestClass implements SQLData { + + private String x; + + @Override + public String getSQLTypeName() throws SQLException { + return null; + } + + @Override + public void readSQL(SQLInput stream, String typeName) throws SQLException { + x = stream.readString(); + } + + @Override + public void writeSQL(SQLOutput stream) throws SQLException {} + } + + @Test + public void testMapStructToObjectWithFactory() throws SQLException { + testMapJson(true); + } + + @Test + public void testMapStructToObjectWithReflection() throws SQLException { + testMapJson(true); + testMapJson(false); + } + @Test + public void testMapArrayToListWithReflection() throws SQLException { + testMapArrayAsList(true); + testMapArrayAsList(false); + } + + @Test + public void testMapArrayWithReflection() throws SQLException { + testMapArray(true); + testMapArray(false); + } + + private void testMapJson(boolean registerFactory) throws SQLException { + if (registerFactory) { + SnowflakeObjectTypeFactories.register(TestClass.class, TestClass::new); + } else { + SnowflakeObjectTypeFactories.unregister(TestClass.class); + } + Connection connection = init(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("select {'x':'a'}::OBJECT(x VARCHAR)"); + resultSet.next(); + TestClass object = resultSet.getObject(1, TestClass.class); + assertEquals("a", object.x); + statement.close(); + connection.close(); + } + private void testMapArrayAsList(boolean registerFactory) throws SQLException { + if (registerFactory) { + SnowflakeObjectTypeFactories.register(TestClass.class, TestClass::new); + } else { + SnowflakeObjectTypeFactories.unregister(TestClass.class); + } + Connection connection = init(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("select [{'x':'aaa'},{'x': 'bbb'}]::ARRAY(OBJECT(x varchar))"); + resultSet.next(); + List objects = resultSet.unwrap(SnowflakeBaseResultSet.class).getList(1, TestClass.class); + assertEquals(objects.get(0).x, "aaa"); + assertEquals(objects.get(1).x, "bbb"); + statement.close(); + connection.close(); + } + private void testMapArray(boolean registerFactory) throws SQLException { + if (registerFactory) { + SnowflakeObjectTypeFactories.register(TestClass.class, TestClass::new); + } else { + SnowflakeObjectTypeFactories.unregister(TestClass.class); + } + Connection connection = init(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("select [{'x':'aaa'},{'x': 'bbb'}]::ARRAY(OBJECT(x varchar))"); + resultSet.next(); + TestClass[] objects = resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, TestClass.class); + assertEquals(objects[0].x, "aaa"); + assertEquals(objects[1].x, "bbb"); + statement.close(); + connection.close(); + } + + @Test + public void testMapStructsFromChunks() throws SQLException { + + Connection connection = init(); + Statement statement = connection.createStatement(); + ResultSet resultSet = + statement.executeQuery( + "select {'x':'a'}::OBJECT(x VARCHAR) FROM TABLE(GENERATOR(ROWCOUNT=>30000))"); + int i = 0; + while (resultSet.next()) { + TestClass object = resultSet.getObject(1, TestClass.class); + assertEquals("a", object.x); + } + statement.close(); + connection.close(); + } + } From 6281099be95a80a115f78844a9121b65c88c20c4 Mon Sep 17 00:00:00 2001 From: Przemyslaw Motacki Date: Fri, 17 Nov 2023 11:14:14 +0100 Subject: [PATCH 13/14] StructuredType simple implementations for List and Map --- .../snowflake/client/jdbc/ResultSetIT.java | 1 - .../client/jdbc/ResultSetLatestIT.java | 56 +++++++++++++++++-- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java index dd4240a4d..7ecb36f2a 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetIT.java @@ -18,7 +18,6 @@ import net.snowflake.client.ConditionalIgnoreRule; import net.snowflake.client.RunningOnGithubAction; import net.snowflake.client.category.TestCategoryResultSet; -import net.snowflake.client.core.structs.SnowflakeObjectTypeFactories; import org.junit.Test; import org.junit.experimental.categories.Category; diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetLatestIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetLatestIT.java index 27f0699e5..14e6e724c 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetLatestIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetLatestIT.java @@ -932,6 +932,7 @@ public void testGranularTimeFunctionsInUTC() throws SQLException { public static class TestClass implements SQLData { private String x; + private String y; @Override public String getSQLTypeName() throws SQLException { @@ -941,6 +942,7 @@ public String getSQLTypeName() throws SQLException { @Override public void readSQL(SQLInput stream, String typeName) throws SQLException { x = stream.readString(); + y = stream.readString(); } @Override @@ -956,6 +958,7 @@ public void testMapStructToObjectWithFactory() throws SQLException { public void testMapStructToObjectWithReflection() throws SQLException { testMapJson(true); testMapJson(false); + testMapMoreStructs(false); } @Test public void testMapArrayToListWithReflection() throws SQLException { @@ -969,6 +972,12 @@ public void testMapArrayWithReflection() throws SQLException { testMapArray(false); } + @Test + public void testMapMapWithReflection() throws SQLException { + testReturnAsMap(true); + testReturnAsMap(false); + } + private void testMapJson(boolean registerFactory) throws SQLException { if (registerFactory) { SnowflakeObjectTypeFactories.register(TestClass.class, TestClass::new); @@ -977,7 +986,7 @@ private void testMapJson(boolean registerFactory) throws SQLException { } Connection connection = init(); Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery("select {'x':'a'}::OBJECT(x VARCHAR)"); + ResultSet resultSet = statement.executeQuery("select {'x':'a', 'y':'abc'}::OBJECT(x VARCHAR, y varchar)"); resultSet.next(); TestClass object = resultSet.getObject(1, TestClass.class); assertEquals("a", object.x); @@ -992,7 +1001,7 @@ private void testMapArrayAsList(boolean registerFactory) throws SQLException { } Connection connection = init(); Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery("select [{'x':'aaa'},{'x': 'bbb'}]::ARRAY(OBJECT(x varchar))"); + ResultSet resultSet = statement.executeQuery("select [{'x':'aaa', 'y':'abc'},{'x': 'bbb', 'y':'abc'}]::ARRAY(OBJECT(x varchar, y varchar))"); resultSet.next(); List objects = resultSet.unwrap(SnowflakeBaseResultSet.class).getList(1, TestClass.class); assertEquals(objects.get(0).x, "aaa"); @@ -1008,7 +1017,7 @@ private void testMapArray(boolean registerFactory) throws SQLException { } Connection connection = init(); Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery("select [{'x':'aaa'},{'x': 'bbb'}]::ARRAY(OBJECT(x varchar))"); + ResultSet resultSet = statement.executeQuery("select [{'x':'aaa', 'y':'abc'},{'x': 'bbb', 'y':'abc'}]::ARRAY(OBJECT(x varchar, y varchar))"); resultSet.next(); TestClass[] objects = resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, TestClass.class); assertEquals(objects[0].x, "aaa"); @@ -1017,6 +1026,45 @@ private void testMapArray(boolean registerFactory) throws SQLException { connection.close(); } + private void testMapMoreStructs(boolean registerFactory) throws SQLException { + if (registerFactory) { + SnowflakeObjectTypeFactories.register(TestClass.class, TestClass::new); + } else { + SnowflakeObjectTypeFactories.unregister(TestClass.class); + } + Connection connection = init(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("select struct from structs"); + resultSet.next(); + TestClass object = resultSet.getObject(1, TestClass.class); + assertEquals("abc", object.x); + assertEquals("def", object.y); + resultSet.next(); + object = resultSet.getObject(1, TestClass.class); + assertEquals("abc", object.x); + assertEquals("def", object.y); + statement.close(); + connection.close(); + } + + private void testReturnAsMap(boolean registerFactory) throws SQLException { + if (registerFactory) { + SnowflakeObjectTypeFactories.register(TestClass.class, TestClass::new); + } else { + SnowflakeObjectTypeFactories.unregister(TestClass.class); + } + Connection connection = init(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("select {'x':{'x':'one', 'y':'abc'},'y':{'x':'two', 'y':'abc'},'z':{'x':'three', 'y':'abc'}}::MAP(VARCHAR, OBJECT(x VARCHAR, y VARCHAR));"); + resultSet.next(); + Map map = resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, TestClass.class); + assertEquals(map.get("x").x, "one"); + assertEquals(map.get("y").x, "two"); + assertEquals(map.get("z").x, "three"); + statement.close(); + connection.close(); + } + @Test public void testMapStructsFromChunks() throws SQLException { @@ -1024,7 +1072,7 @@ public void testMapStructsFromChunks() throws SQLException { Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery( - "select {'x':'a'}::OBJECT(x VARCHAR) FROM TABLE(GENERATOR(ROWCOUNT=>30000))"); + "select {'x':'a', 'y':'abc'}::OBJECT(x VARCHAR, y VARCHAR) FROM TABLE(GENERATOR(ROWCOUNT=>30000))"); int i = 0; while (resultSet.next()) { TestClass object = resultSet.getObject(1, TestClass.class); From 6d34b18f77235c425f7935614aed08af35cbe88d Mon Sep 17 00:00:00 2001 From: Przemyslaw Motacki Date: Mon, 20 Nov 2023 08:20:37 +0100 Subject: [PATCH 14/14] StructuredType add types in writing process --- .../jdbc/SnowflakePreparedStatementV1.java | 5 ++ .../snowflake/client/jdbc/SnowflakeType.java | 3 + .../snowflake/client/jdbc/BindingDataIT.java | 76 +++++++++++++++++-- .../client/jdbc/ResultSetLatestIT.java | 1 + 4 files changed, 77 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakePreparedStatementV1.java b/src/main/java/net/snowflake/client/jdbc/SnowflakePreparedStatementV1.java index aba023e4e..68842b314 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakePreparedStatementV1.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakePreparedStatementV1.java @@ -403,6 +403,9 @@ public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQ setTime(parameterIndex, (Time) x); } else if (targetSqlType == Types.TIMESTAMP) { setTimestamp(parameterIndex, (Timestamp) x); + } else if (targetSqlType == Types.STRUCT) { + ParameterBindingDTO binding = new ParameterBindingDTO(SnowflakeUtil.javaTypeToSFTypeString(targetSqlType, connection.getSFBaseSession()), x); + parameterBindings.put(String.valueOf(parameterIndex), binding); } else if (targetSqlType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ || targetSqlType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_NTZ) { setTimestampWithType(parameterIndex, (Timestamp) x, targetSqlType); @@ -450,6 +453,8 @@ public void setObject(int parameterIndex, Object x) throws SQLException { setBoolean(parameterIndex, (Boolean) x); } else if (x instanceof byte[]) { setBytes(parameterIndex, (byte[]) x); + } else if (x instanceof SQLData) { + setObject(parameterIndex, x, Types.STRUCT); } else { throw new SnowflakeSQLLoggedException( connection.getSFBaseSession(), diff --git a/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java b/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java index 1226b359f..ee6d3c418 100644 --- a/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java +++ b/src/main/java/net/snowflake/client/jdbc/SnowflakeType.java @@ -420,6 +420,9 @@ public static SnowflakeType javaTypeToSFType(int javaType, SFBaseSession session case Types.BOOLEAN: return BOOLEAN; + case Types.STRUCT: + return OBJECT; + case Types.NULL: return ANY; diff --git a/src/test/java/net/snowflake/client/jdbc/BindingDataIT.java b/src/test/java/net/snowflake/client/jdbc/BindingDataIT.java index 8ad65aa99..7cb9e09d1 100644 --- a/src/test/java/net/snowflake/client/jdbc/BindingDataIT.java +++ b/src/test/java/net/snowflake/client/jdbc/BindingDataIT.java @@ -9,14 +9,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import java.sql.Connection; -import java.sql.Date; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.sql.Time; -import java.sql.Types; +import java.sql.*; import java.util.Calendar; import java.util.TimeZone; import net.snowflake.client.AbstractDriverIT; @@ -362,6 +355,73 @@ public void testBindDateWithCalendar(Date dateValue) throws SQLException { statement.execute("drop table if exists test_bind_date"); connection.close(); } + public static class TestClass implements SQLData { + + private String x; + private String y; + + public TestClass(String x, String y) { + this.x = x; + this.y = y; + } + + public String getX() { + return x; + } + + public void setX(String x) { + this.x = x; + } + + public String getY() { + return y; + } + + public void setY(String y) { + this.y = y; + } + + @Override + public String getSQLTypeName() throws SQLException { + return null; + } + + @Override + public void readSQL(SQLInput stream, String typeName) throws SQLException { + x = stream.readString(); + y = stream.readString(); + } + + @Override + public void writeSQL(SQLOutput stream) throws SQLException {} + } + +// @Theory +// public void testBindStruct() throws SQLException { +// TestClass testClass = new TestClass("ABC", "DEF"); +// +// Connection connection = getConnection(); +// Statement statement = connection.createStatement(); +// statement.execute("CREATE or replace TABLE structs_test (struct OBJECT(x VARCHAR, y VARCHAR))"); +// +// PreparedStatement preparedStatement = +//// connection.prepareStatement("insert into test_bind_date values (?)"); +// connection.prepareStatement("INSERT INTO structs_test (struct) SELECT (?)"); +// preparedStatement.setObject(1, testClass); +// preparedStatement.executeUpdate(); +// +// preparedStatement = connection.prepareStatement("select * from structs_test"); +// +// ResultSet resultSet = preparedStatement.executeQuery(); +// assertThat(resultSet.next(), is(true)); +//// assertThat(resultSet.getDate("C1"), is(dateValue)); +// +// resultSet.close(); +// preparedStatement.close(); +// +//// statement.execute("drop table if exists structs_test"); +// connection.close(); +// } @Theory public void testBindObjectWithScaleZero(int intValue) throws SQLException { diff --git a/src/test/java/net/snowflake/client/jdbc/ResultSetLatestIT.java b/src/test/java/net/snowflake/client/jdbc/ResultSetLatestIT.java index 14e6e724c..b719c0b9e 100644 --- a/src/test/java/net/snowflake/client/jdbc/ResultSetLatestIT.java +++ b/src/test/java/net/snowflake/client/jdbc/ResultSetLatestIT.java @@ -993,6 +993,7 @@ private void testMapJson(boolean registerFactory) throws SQLException { statement.close(); connection.close(); } + private void testMapArrayAsList(boolean registerFactory) throws SQLException { if (registerFactory) { SnowflakeObjectTypeFactories.register(TestClass.class, TestClass::new);