Skip to content

Commit d4ca9f0

Browse files
SNOW-970859 Implement getObject for structured types for JSON response (#1624)
* SNOW-970859 Implement getObject for structured types for JSON response * SNOW-974576 Create FieldsMetadata * SNOW-974576 Add JsonConverters in ArrowResultSet for struct --------- Co-authored-by: Przemyslaw Motacki <[email protected]>
1 parent f16a35b commit d4ca9f0

29 files changed

+1424
-94
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) 2012-2024 Snowflake Computing Inc. All right reserved.
3+
*/
4+
package net.snowflake.client.core;
5+
6+
import java.sql.Types;
7+
import net.snowflake.client.jdbc.SnowflakeUtil;
8+
9+
@SnowflakeJdbcInternalApi
10+
public class ColumnTypeHelper {
11+
public static int getColumnType(int internalColumnType, SFBaseSession session) {
12+
int externalColumnType = internalColumnType;
13+
14+
if (internalColumnType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ) {
15+
externalColumnType = Types.TIMESTAMP;
16+
} else if (internalColumnType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ) {
17+
externalColumnType =
18+
session == null
19+
? Types.TIMESTAMP_WITH_TIMEZONE
20+
: session.getEnableReturnTimestampWithTimeZone()
21+
? Types.TIMESTAMP_WITH_TIMEZONE
22+
: Types.TIMESTAMP;
23+
}
24+
return externalColumnType;
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
/*
2+
* Copyright (c) 2012-2024 Snowflake Computing Inc. All right reserved.
3+
*/
4+
package net.snowflake.client.core;
5+
6+
import com.fasterxml.jackson.databind.JsonNode;
7+
import java.io.InputStream;
8+
import java.io.Reader;
9+
import java.math.BigDecimal;
10+
import java.net.URL;
11+
import java.sql.Array;
12+
import java.sql.Blob;
13+
import java.sql.Clob;
14+
import java.sql.Date;
15+
import java.sql.NClob;
16+
import java.sql.Ref;
17+
import java.sql.RowId;
18+
import java.sql.SQLData;
19+
import java.sql.SQLException;
20+
import java.sql.SQLXML;
21+
import java.sql.Time;
22+
import java.sql.Timestamp;
23+
import java.sql.Types;
24+
import java.time.Instant;
25+
import java.time.ZoneOffset;
26+
import java.util.Iterator;
27+
import java.util.List;
28+
import java.util.TimeZone;
29+
import net.snowflake.client.core.json.Converters;
30+
import net.snowflake.client.core.structs.SQLDataCreationHelper;
31+
import net.snowflake.client.jdbc.FieldMetadata;
32+
import net.snowflake.client.jdbc.SnowflakeLoggedFeatureNotSupportedException;
33+
import net.snowflake.client.jdbc.SnowflakeUtil;
34+
import net.snowflake.client.util.ThrowingCallable;
35+
import net.snowflake.client.util.ThrowingTriFunction;
36+
import net.snowflake.common.core.SFTimestamp;
37+
import net.snowflake.common.core.SnowflakeDateTimeFormat;
38+
39+
@SnowflakeJdbcInternalApi
40+
public class JsonSqlInput implements SFSqlInput {
41+
private final JsonNode input;
42+
private final Iterator<JsonNode> elements;
43+
private final SFBaseSession session;
44+
private final Converters converters;
45+
private final List<FieldMetadata> fields;
46+
private int currentIndex = 0;
47+
48+
public JsonSqlInput(
49+
JsonNode input, SFBaseSession session, Converters converters, List<FieldMetadata> fields) {
50+
this.input = input;
51+
this.elements = input.elements();
52+
this.session = session;
53+
this.converters = converters;
54+
this.fields = fields;
55+
}
56+
57+
public JsonNode getInput() {
58+
return input;
59+
}
60+
61+
@Override
62+
public String readString() throws SQLException {
63+
return withNextValue(
64+
((value, jsonNode, fieldMetadata) -> {
65+
int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);
66+
int columnSubType = fieldMetadata.getType();
67+
int scale = fieldMetadata.getScale();
68+
return mapExceptions(
69+
() ->
70+
converters
71+
.getStringConverter()
72+
.getString(value, columnType, columnSubType, scale));
73+
}));
74+
}
75+
76+
@Override
77+
public boolean readBoolean() throws SQLException {
78+
return withNextValue(
79+
(value, jsonNode, fieldMetadata) -> {
80+
int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);
81+
return mapExceptions(
82+
() -> converters.getBooleanConverter().getBoolean(value, columnType));
83+
});
84+
}
85+
86+
@Override
87+
public byte readByte() throws SQLException {
88+
return withNextValue(
89+
(value, jsonNode, fieldMetadata) ->
90+
mapExceptions(() -> converters.getNumberConverter().getByte(value)));
91+
}
92+
93+
@Override
94+
public short readShort() throws SQLException {
95+
return withNextValue(
96+
(value, jsonNode, fieldMetadata) -> {
97+
int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);
98+
return mapExceptions(() -> converters.getNumberConverter().getShort(value, columnType));
99+
});
100+
}
101+
102+
@Override
103+
public int readInt() throws SQLException {
104+
return withNextValue(
105+
(value, jsonNode, fieldMetadata) -> {
106+
int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);
107+
return mapExceptions(() -> converters.getNumberConverter().getInt(value, columnType));
108+
});
109+
}
110+
111+
@Override
112+
public long readLong() throws SQLException {
113+
return withNextValue(
114+
(value, jsonNode, fieldMetadata) -> {
115+
int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);
116+
return mapExceptions(() -> converters.getNumberConverter().getLong(value, columnType));
117+
});
118+
}
119+
120+
@Override
121+
public float readFloat() throws SQLException {
122+
return withNextValue(
123+
(value, jsonNode, fieldMetadata) -> {
124+
int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);
125+
return mapExceptions(() -> converters.getNumberConverter().getFloat(value, columnType));
126+
});
127+
}
128+
129+
@Override
130+
public double readDouble() throws SQLException {
131+
return withNextValue(
132+
(value, jsonNode, fieldMetadata) -> {
133+
int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);
134+
return mapExceptions(() -> converters.getNumberConverter().getDouble(value, columnType));
135+
});
136+
}
137+
138+
@Override
139+
public BigDecimal readBigDecimal() throws SQLException {
140+
return withNextValue(
141+
(value, jsonNode, fieldMetadata) -> {
142+
int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);
143+
return mapExceptions(
144+
() -> converters.getNumberConverter().getBigDecimal(value, columnType));
145+
});
146+
}
147+
148+
@Override
149+
public byte[] readBytes() throws SQLException {
150+
return withNextValue(
151+
(value, jsonNode, fieldMetadata) -> {
152+
int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);
153+
int columnSubType = fieldMetadata.getType();
154+
int scale = fieldMetadata.getScale();
155+
return mapExceptions(
156+
() ->
157+
converters.getBytesConverter().getBytes(value, columnType, columnSubType, scale));
158+
});
159+
}
160+
161+
@Override
162+
public Date readDate() throws SQLException {
163+
return withNextValue(
164+
(value, jsonNode, fieldMetadata) -> {
165+
SnowflakeDateTimeFormat formatter = getFormat(session, "DATE_OUTPUT_FORMAT");
166+
SFTimestamp timestamp = formatter.parse((String) value);
167+
return Date.valueOf(
168+
Instant.ofEpochMilli(timestamp.getTime()).atZone(ZoneOffset.UTC).toLocalDate());
169+
});
170+
}
171+
172+
@Override
173+
public Time readTime() throws SQLException {
174+
return withNextValue(
175+
(value, jsonNode, fieldMetadata) -> {
176+
SnowflakeDateTimeFormat formatter = getFormat(session, "TIME_OUTPUT_FORMAT");
177+
SFTimestamp timestamp = formatter.parse((String) value);
178+
return Time.valueOf(
179+
Instant.ofEpochMilli(timestamp.getTime()).atZone(ZoneOffset.UTC).toLocalTime());
180+
});
181+
}
182+
183+
@Override
184+
public Timestamp readTimestamp() throws SQLException {
185+
return readTimestamp(null);
186+
}
187+
188+
@Override
189+
public Timestamp readTimestamp(TimeZone tz) throws SQLException {
190+
return withNextValue(
191+
(value, jsonNode, fieldMetadata) -> {
192+
if (value == null) {
193+
return null;
194+
}
195+
int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);
196+
int columnSubType = fieldMetadata.getType();
197+
int scale = fieldMetadata.getScale();
198+
Timestamp result = getTimestampFromType(columnSubType, (String) value);
199+
if (result != null) {
200+
return result;
201+
}
202+
return mapExceptions(
203+
() ->
204+
converters
205+
.getDateTimeConverter()
206+
.getTimestamp(value, columnType, columnSubType, tz, scale));
207+
});
208+
}
209+
210+
private Timestamp getTimestampFromType(int columnSubType, String value) {
211+
if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_LTZ) {
212+
return getTimestampFromFormat("TIMESTAMP_LTZ_OUTPUT_FORMAT", value);
213+
} else if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_NTZ
214+
|| columnSubType == Types.TIMESTAMP) {
215+
return getTimestampFromFormat("TIMESTAMP_NTZ_OUTPUT_FORMAT", value);
216+
} else if (columnSubType == SnowflakeUtil.EXTRA_TYPES_TIMESTAMP_TZ) {
217+
return getTimestampFromFormat("TIMESTAMP_TZ_OUTPUT_FORMAT", value);
218+
} else {
219+
return null;
220+
}
221+
}
222+
223+
private Timestamp getTimestampFromFormat(String format, String value) {
224+
String rawFormat = (String) session.getCommonParameters().get(format);
225+
if (rawFormat == null || rawFormat.isEmpty()) {
226+
rawFormat = (String) session.getCommonParameters().get("TIMESTAMP_OUTPUT_FORMAT");
227+
}
228+
SnowflakeDateTimeFormat formatter = SnowflakeDateTimeFormat.fromSqlFormat(rawFormat);
229+
return formatter.parse(value).getTimestamp();
230+
}
231+
232+
@Override
233+
public Reader readCharacterStream() throws SQLException {
234+
throw new SnowflakeLoggedFeatureNotSupportedException(session, "readCharacterStream");
235+
}
236+
237+
@Override
238+
public InputStream readAsciiStream() throws SQLException {
239+
throw new SnowflakeLoggedFeatureNotSupportedException(session, "readAsciiStream");
240+
}
241+
242+
@Override
243+
public InputStream readBinaryStream() throws SQLException {
244+
throw new SnowflakeLoggedFeatureNotSupportedException(session, "readBinaryStream");
245+
}
246+
247+
@Override
248+
public Object readObject() throws SQLException {
249+
// TODO structuredType return map - SNOW-974575
250+
throw new SnowflakeLoggedFeatureNotSupportedException(session, "readObject");
251+
}
252+
253+
@Override
254+
public Ref readRef() throws SQLException {
255+
throw new SnowflakeLoggedFeatureNotSupportedException(session, "readRef");
256+
}
257+
258+
@Override
259+
public Blob readBlob() throws SQLException {
260+
throw new SnowflakeLoggedFeatureNotSupportedException(session, "readBlob");
261+
}
262+
263+
@Override
264+
public Clob readClob() throws SQLException {
265+
throw new SnowflakeLoggedFeatureNotSupportedException(session, "readClob");
266+
}
267+
268+
@Override
269+
public Array readArray() throws SQLException {
270+
throw new SnowflakeLoggedFeatureNotSupportedException(session, "readArray");
271+
}
272+
273+
@Override
274+
public boolean wasNull() throws SQLException {
275+
return false; // nulls are not allowed in structure types
276+
}
277+
278+
@Override
279+
public URL readURL() throws SQLException {
280+
throw new SnowflakeLoggedFeatureNotSupportedException(session, "readURL");
281+
}
282+
283+
@Override
284+
public NClob readNClob() throws SQLException {
285+
throw new SnowflakeLoggedFeatureNotSupportedException(session, "readNClob");
286+
}
287+
288+
@Override
289+
public String readNString() throws SQLException {
290+
throw new SnowflakeLoggedFeatureNotSupportedException(session, "readNString");
291+
}
292+
293+
@Override
294+
public SQLXML readSQLXML() throws SQLException {
295+
throw new SnowflakeLoggedFeatureNotSupportedException(session, "readSQLXML");
296+
}
297+
298+
@Override
299+
public RowId readRowId() throws SQLException {
300+
throw new SnowflakeLoggedFeatureNotSupportedException(session, "readRowId");
301+
}
302+
303+
@Override
304+
public <T> T readObject(Class<T> type) throws SQLException {
305+
return withNextValue(
306+
(__, jsonNode, fieldMetadata) -> {
307+
SQLData instance = (SQLData) SQLDataCreationHelper.create(type);
308+
instance.readSQL(
309+
new JsonSqlInput(jsonNode, session, converters, fieldMetadata.getFields()), null);
310+
return (T) instance;
311+
});
312+
}
313+
314+
private <T> T withNextValue(
315+
ThrowingTriFunction<Object, JsonNode, FieldMetadata, T, SQLException> action)
316+
throws SQLException {
317+
JsonNode jsonNode = elements.next();
318+
Object value = getValue(jsonNode);
319+
return action.apply(value, jsonNode, fields.get(currentIndex++));
320+
}
321+
322+
private Object getValue(JsonNode jsonNode) {
323+
if (jsonNode.isTextual()) {
324+
return jsonNode.textValue();
325+
} else if (jsonNode.isBoolean()) {
326+
return jsonNode.booleanValue();
327+
} else if (jsonNode.isNumber()) {
328+
return jsonNode.numberValue();
329+
}
330+
return null;
331+
}
332+
333+
private <T> T mapExceptions(ThrowingCallable<T, SFException> action) throws SQLException {
334+
try {
335+
return action.call();
336+
} catch (SFException e) {
337+
throw new SQLException(e);
338+
}
339+
}
340+
341+
private static SnowflakeDateTimeFormat getFormat(SFBaseSession session, String format) {
342+
return SnowflakeDateTimeFormat.fromSqlFormat(
343+
(String) session.getCommonParameters().get(format));
344+
}
345+
}

0 commit comments

Comments
 (0)