Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.sql.Timestamp;
import java.util.Base64;
import java.util.Map;
import org.apache.arrow.vector.util.Text;

/**
* An abstract implementation of {@link java.sql.Struct} used as a base class for {@link
Expand Down Expand Up @@ -86,30 +87,24 @@ public String toString() {
Object value = attributes[i];

sb.append("\"").append(fieldName.replace("\"", "\\\"")).append("\":");

if (value == null) {
sb.append("null");
} else if (value instanceof String || value instanceof org.apache.arrow.vector.util.Text) {
String stringValue = value.toString().replace("\"", "\\\"");
sb.append("\"").append(stringValue).append("\"");
} else if (value instanceof Timestamp
&& field.getType().getStandardType() == StandardSQLTypeName.TIMESTAMP) {
sb.append("\"")
.append(BigQueryTemporalUtility.formatTimestampToUtcString((Timestamp) value))
.append("\"");
} else if (value instanceof Time
&& field.getType().getStandardType() == StandardSQLTypeName.TIME) {
sb.append("\"")
.append(
BigQueryTypeRegistry.convert(
getRawAttribute(i), StandardSQLTypeName.TIME, String.class, null))
.append("\"");
} else if (value instanceof Timestamp || value instanceof Date || value instanceof Time) {
sb.append("\"").append(value.toString()).append("\"");
} else if (value instanceof byte[]) {
sb.append("\"").append(Base64.getEncoder().encodeToString((byte[]) value)).append("\"");
if (value instanceof BigQueryBaseArray) {
BigQueryBaseArray arrayVal = (BigQueryBaseArray) value;
Object[] elements = (Object[]) arrayVal.getArray();
if (elements == null) {
sb.append("null");
} else {
sb.append("[");
for (int j = 0; j < elements.length; j++) {
if (j > 0) {
sb.append(",");
}
appendJsonValue(
sb, field, elements[j], elements[j] == null ? null : arrayVal.getRawElement(j));
}
sb.append("]");
}
} else {
sb.append(value.toString());
appendJsonValue(sb, field, value, value == null ? null : getRawAttribute(i));
}
}
sb.append("}");
Expand All @@ -119,4 +114,33 @@ public String toString() {
return "{ \"error\": \"Error converting struct to string: " + e.getMessage() + "\" }";
}
}

private static void appendJsonValue(StringBuilder sb, Field field, Object value, Object rawValue)
throws SQLException {
if (value == null) {
sb.append("null");
} else if (field.getType().getStandardType() == StandardSQLTypeName.JSON) {
sb.append(value);
} else if (value instanceof String || value instanceof Text) {
String stringValue = value.toString().replace("\"", "\\\"");
sb.append("\"").append(stringValue).append("\"");
} else if (value instanceof Timestamp
&& field.getType().getStandardType() == StandardSQLTypeName.TIMESTAMP) {
sb.append("\"")
.append(BigQueryTemporalUtility.formatTimestampToUtcString((Timestamp) value))
.append("\"");
} else if (value instanceof Time
&& field.getType().getStandardType() == StandardSQLTypeName.TIME) {
sb.append("\"")
.append(
BigQueryTypeRegistry.convert(rawValue, StandardSQLTypeName.TIME, String.class, null))
.append("\"");
} else if (value instanceof Timestamp || value instanceof Date || value instanceof Time) {
sb.append("\"").append(value).append("\"");
} else if (value instanceof byte[]) {
sb.append("\"").append(Base64.getEncoder().encodeToString((byte[]) value)).append("\"");
} else {
sb.append(value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,20 @@ GEOGRAPHY, new Text("POINT(-122 47)"), new Text("POINT(-122 48)")),
.isEqualTo(new String[] {"POINT(-122 47)", "POINT(-122 48)"});
assertThat(((Array) attributes[11]).getArray())
.isEqualTo(new byte[][] {"one".getBytes(), "two".getBytes()});
assertThat(struct.toString())
.isEqualTo(
"{\"INT64_arr_column\":[10,20],"
+ "\"BOOL_arr_column\":[true,false],"
+ "\"FLOAT64_arr_column\":[11.2,33.4],"
+ "\"NUMERIC_arr_column\":[11.2657,33.4657],"
+ "\"BIGNUMERIC_arr_column\":[11.2657,33.4657],"
+ "\"STRING_arr_column\":[\"one\",\"two\"],"
+ "\"TIMESTAMP_arr_column\":[\"2023-03-30 11:14:19.820227\",\"2023-03-31 11:14:19.820227\"],"
+ "\"DATE_arr_column\":[\"2023-03-30\",\"2023-03-31\"],"
+ "\"TIME_arr_column\":[\"11:14:19.820227\",\"11:14:20.820227\"],"
+ "\"DATETIME_arr_column\":[\"2023-03-30 11:14:19.820227\",\"2023-03-30 11:15:19.820227\"],"
+ "\"GEOGRAPHY_arr_column\":[\"POINT(-122 47)\",\"POINT(-122 48)\"],"
+ "\"BYTES_arr_column\":[\"b25l\",\"dHdv\"]}");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,20 @@ public void structOfArrays() throws SQLException {
.isEqualTo(new String[] {"POINT(-122 47)", "POINT(-122 48)"});
assertThat(((Array) attributes[11]).getArray())
.isEqualTo(new byte[][] {"one".getBytes(), "two".getBytes()});
assertThat(struct.toString())
.isEqualTo(
"{\"INT64_arr_column\":[10,20],"
+ "\"BOOL_arr_column\":[true,false],"
+ "\"FLOAT64_arr_column\":[11.2,33.4],"
+ "\"NUMERIC_arr_column\":[11.2657,33.4657],"
+ "\"BIGNUMERIC_arr_column\":[11.2657,33.4657],"
+ "\"STRING_arr_column\":[\"one\",\"two\"],"
+ "\"TIMESTAMP_arr_column\":[\"2023-03-30 11:14:19.82\",\"2023-03-31 11:14:19.82\"],"
+ "\"DATE_arr_column\":[\"2023-03-30\",\"2023-03-31\"],"
+ "\"TIME_arr_column\":[\"11:14:19.82\",\"11:14:20.82\"],"
+ "\"DATETIME_arr_column\":[\"2023-03-30 11:14:19.82\",\"2023-03-30 11:15:19.82\"],"
+ "\"GEOGRAPHY_arr_column\":[\"POINT(-122 47)\",\"POINT(-122 48)\"],"
+ "\"BYTES_arr_column\":[\"b25l\",\"dHdv\"]}");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

public class ITBigQueryJDBCTest extends ITBase {
static final String PROJECT_ID = ServiceOptions.getDefaultProjectId();
Expand Down Expand Up @@ -3095,4 +3097,33 @@ public void testSessionAbortedOnConnectionClose() throws SQLException {
}
}
}

@ParameterizedTest
@ValueSource(strings = {"", FORCE_READ_API_PROPERTIES})
public void testStructWithNestedArraysValidJson(String extraProps) throws SQLException {
String sql =
"SELECT STRUCT("
+ "1 AS id, "
+ "['hello', 'world'] AS str_arr, "
+ "[DATE '2026-01-01', DATE '2026-01-02'] AS date_arr, "
+ "[TIME '12:34:56.789012'] AS time_arr, "
+ "[TIMESTAMP '2026-01-01 00:00:00 UTC'] AS ts_arr, "
+ "[b'abc', b'def'] AS bytes_arr"
+ ") AS s_nested_arrays";

String expectedJson =
"{\"id\":1,"
+ "\"str_arr\":[\"hello\",\"world\"],"
+ "\"date_arr\":[\"2026-01-01\",\"2026-01-02\"],"
+ "\"time_arr\":[\"12:34:56.789012\"],"
+ "\"ts_arr\":[\"2026-01-01 00:00:00.0\"],"
+ "\"bytes_arr\":[\"YWJj\",\"ZGVm\"]}";

try (Connection conn = DriverManager.getConnection(connection_uri + extraProps);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
assertTrue(rs.next());
assertEquals(expectedJson, rs.getString("s_nested_arrays"));
}
}
}
Loading