diff --git a/src/integrationTest/java/integration/tests/PreparedStatementTest.java b/src/integrationTest/java/integration/tests/PreparedStatementTest.java index bae01c915..b8c38f412 100644 --- a/src/integrationTest/java/integration/tests/PreparedStatementTest.java +++ b/src/integrationTest/java/integration/tests/PreparedStatementTest.java @@ -423,6 +423,40 @@ void shouldInsertAndSelectGeography() throws SQLException { } } + @Test + @Tag("v2") + void shouldInsertAndSelectStruct() throws SQLException { + Car car1 = Car.builder().make("Ford").sales(12345).ts(new Timestamp(2)).d(new Date(3)).build(); + + // TODO: use prepared statement ddl for more complex type + executeStatementFromFile("/statements/statement/ddl.sql"); + try (Connection connection = createConnection()) { + + try (PreparedStatement statement = connection + .prepareStatement("INSERT INTO statement_test (id) VALUES (?)")) { + statement.setLong(1, car1.getSales()); + // statement.setString(2, car1.getMake()); + // statement.setTimestamp(3, car1.getTs()); + // statement.setDate(4, car1.getD()); + statement.executeUpdate(); + } + setParam(connection, "advanced_mode", "true"); + setParam(connection, "enable_row_selection", "true"); + try (Statement statement = connection.createStatement(); + ResultSet rs = statement + .executeQuery("SELECT statement_test FROM statement_test")) { + rs.next(); + assertEquals(FireboltDataType.STRUCT.name().toLowerCase() + "(id long null)", + rs.getMetaData().getColumnTypeName(1).toLowerCase()); + String expectedJson = String.format("{\"id\":\"%d\"}", + car1.getSales()); + assertEquals(expectedJson, rs.getString(1)); + } + } finally { + executeStatementFromFile("/statements/statement/cleanup.sql"); + } + } + private QueryResult createExpectedResult(List> expectedRows) { return QueryResult.builder().databaseName(ConnectionInfo.getInstance().getDatabase()) .tableName("prepared_statement_test") diff --git a/src/main/java/com/firebolt/jdbc/resultset/column/ColumnType.java b/src/main/java/com/firebolt/jdbc/resultset/column/ColumnType.java index 4e1d23a21..a38a81994 100644 --- a/src/main/java/com/firebolt/jdbc/resultset/column/ColumnType.java +++ b/src/main/java/com/firebolt/jdbc/resultset/column/ColumnType.java @@ -21,6 +21,7 @@ import java.util.stream.Collectors; import static com.firebolt.jdbc.type.FireboltDataType.ARRAY; +import static com.firebolt.jdbc.type.FireboltDataType.STRUCT; import static com.firebolt.jdbc.type.FireboltDataType.TUPLE; import static com.firebolt.jdbc.type.FireboltDataType.ofType; @@ -62,6 +63,8 @@ public static ColumnType of(String columnType) { innerDataTypes = getCollectionSubType(FireboltDataType.ARRAY, typeWithoutNullKeyword); } else if (isType(FireboltDataType.TUPLE, typeWithoutNullKeyword)) { innerDataTypes = getCollectionSubType(FireboltDataType.TUPLE, typeWithoutNullKeyword); + } else if (isType(FireboltDataType.STRUCT, typeWithoutNullKeyword)) { + innerDataTypes = getCollectionSubType(FireboltDataType.STRUCT, typeWithoutNullKeyword); } int typeEndIndex = getTypeEndPosition(typeWithoutNullKeyword); @@ -108,6 +111,8 @@ private static List getCollectionSubType(FireboltDataType fireboltDa if (fireboltDataType.equals(TUPLE)) { types = typeWithoutNullKeyword.split(",(?![^()]*\\))"); // Regex to split on comma and ignoring comma that are between // parenthesis + } else if (fireboltDataType.equals(STRUCT)) { + types = typeWithoutNullKeyword.split(","); } else { types = new String[] {typeWithoutNullKeyword}; } @@ -177,6 +182,8 @@ public String getCompactTypeName() { return getArrayCompactTypeName(); } else if (isTuple()) { return getTupleCompactTypeName(innerTypes); + } else if (isStruct()) { + return name; } else { return dataType.getDisplayName(); } @@ -209,6 +216,10 @@ private boolean isTuple() { return dataType.equals(TUPLE); } + private boolean isStruct() { + return dataType.equals(STRUCT); + } + public ColumnType getArrayBaseColumnType() { if (innerTypes == null || innerTypes.isEmpty()) { return null; diff --git a/src/main/java/com/firebolt/jdbc/type/FireboltDataType.java b/src/main/java/com/firebolt/jdbc/type/FireboltDataType.java index acad47600..60ff5cdab 100644 --- a/src/main/java/com/firebolt/jdbc/type/FireboltDataType.java +++ b/src/main/java/com/firebolt/jdbc/type/FireboltDataType.java @@ -42,7 +42,8 @@ public enum FireboltDataType { TUPLE(Types.OTHER, FireboltDataTypeDisplayNames.TUPLE, BaseType.OBJECT, false, true, 0, 0, 0, false,"Tuple"), BYTEA(Types.BINARY, FireboltDataTypeDisplayNames.BYTEA, BaseType.BYTEA, false, true, 0, 0, 0, false, "ByteA"), GEOGRAPHY(Types.VARCHAR, FireboltDataTypeDisplayNames.GEOGRAPHY, BaseType.TEXT, false, false, 0, 0, 0, false, - "Geography"); + "Geography"), + STRUCT(Types.VARCHAR, FireboltDataTypeDisplayNames.STRUCT, BaseType.TEXT, false, false, 0, 0, 0, false, "Struct"); private static final Map typeNameOrAliasToType; diff --git a/src/main/java/com/firebolt/jdbc/type/FireboltDataTypeDisplayNames.java b/src/main/java/com/firebolt/jdbc/type/FireboltDataTypeDisplayNames.java index 23e01b680..18f9da828 100644 --- a/src/main/java/com/firebolt/jdbc/type/FireboltDataTypeDisplayNames.java +++ b/src/main/java/com/firebolt/jdbc/type/FireboltDataTypeDisplayNames.java @@ -22,4 +22,5 @@ public class FireboltDataTypeDisplayNames { static final String TUPLE = "tuple"; static final String BYTEA = "bytea"; static final String GEOGRAPHY = "geography"; + static final String STRUCT = "struct"; } \ No newline at end of file diff --git a/src/test/java/com/firebolt/jdbc/resultset/FireboltResultSetTest.java b/src/test/java/com/firebolt/jdbc/resultset/FireboltResultSetTest.java index 407a4e45a..3a78a13e8 100644 --- a/src/test/java/com/firebolt/jdbc/resultset/FireboltResultSetTest.java +++ b/src/test/java/com/firebolt/jdbc/resultset/FireboltResultSetTest.java @@ -1478,6 +1478,26 @@ void shouldReturnGeography() throws SQLException { assertEquals(Types.VARCHAR, resultSet.getMetaData().getColumnType(9)); } + @Test + void shouldReturnStruct() throws SQLException { + inputStream = getInputStreamWithStruct(); + resultSet = createResultSet(inputStream); + resultSet.next(); + // TODO: is this correct null handling? + assertEquals("{\"a\": N}", resultSet.getObject(3)); + assertEquals("{\"a\": N}", resultSet.getObject("an_empty_struct")); + assertEquals("{\"a\": 1}", resultSet.getObject(4)); + assertEquals("{\"a\": 1}", resultSet.getObject("a_struct")); + // Returns native JDBC type + for (int i = 3; i <= 5; i++) { + assertEquals(Types.VARCHAR, resultSet.getMetaData().getColumnType(i)); + } + + assertEquals("STRUCT(A INT NULL)", resultSet.getMetaData().getColumnTypeName(3)); + assertEquals("STRUCT(A INT)", resultSet.getMetaData().getColumnTypeName(4)); + assertEquals("STRUCT(A INT)", resultSet.getMetaData().getColumnTypeName(5)); + } + @Test void shouldBeCaseInsensitive() throws SQLException { inputStream = getInputStreamWithCommonResponseExample(); @@ -1552,6 +1572,10 @@ private InputStream getInputStreamWithArray() { return FireboltResultSetTest.class.getResourceAsStream("/responses/firebolt-response-with-array"); } + private InputStream getInputStreamWithStruct() { + return FireboltResultSetTest.class.getResourceAsStream("/responses/firebolt-response-with-struct-nofalse"); + } + private ResultSet createResultSet(InputStream is) throws SQLException { return new FireboltResultSet(is, "a_table", "a_db", 65535, false, fireboltStatement, true); }