diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java index 9216732b49b2..ee36184e4b5b 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java @@ -207,7 +207,7 @@ protected SQLException createCoercionException( cause)); } - private StandardSQLTypeName getStandardSQLTypeName(int columnIndex) throws SQLException { + protected StandardSQLTypeName getStandardSQLTypeName(int columnIndex) throws SQLException { checkClosed(); if (isNested) { if (columnIndex == 1) { diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java index 0dbda843d1e1..8a18ecb46a6e 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java @@ -25,6 +25,7 @@ import com.google.cloud.bigquery.FieldValue.Attribute; import com.google.cloud.bigquery.Job; import com.google.cloud.bigquery.Schema; +import com.google.cloud.bigquery.StandardSQLTypeName; import com.google.cloud.bigquery.exception.BigQueryJdbcRuntimeException; import java.sql.ResultSet; import java.sql.SQLException; @@ -214,6 +215,23 @@ public boolean next() throws SQLException { } } + @Override + public String getString(int columnIndex) throws SQLException { + checkClosed(); + StandardSQLTypeName type = getStandardSQLTypeName(columnIndex); + if (type != StandardSQLTypeName.TIMESTAMP) { + return super.getString(columnIndex); + } + FieldValue value = getObjectInternal(columnIndex); + if (value == null || value.isNull()) { + return null; + } + if (value.getAttribute() == Attribute.REPEATED || value.getAttribute() == Attribute.RECORD) { + return super.getString(columnIndex); + } + return BigQueryTemporalUtility.formatTimestampString(value.getStringValue()); + } + @Override public Object getObject(int columnIndex) throws SQLException { // columnIndex is SQL index starting at 1 diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java index b26cf78bac0a..b2baa550858c 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java @@ -16,6 +16,9 @@ package com.google.cloud.bigquery.jdbc; +import com.google.common.base.Strings; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; @@ -24,6 +27,8 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.Calendar; /** @@ -32,6 +37,9 @@ */ final class BigQueryTemporalUtility { + private static final DateTimeFormatter UTC_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneOffset.UTC); + private BigQueryTemporalUtility() {} /** @@ -89,6 +97,28 @@ public static Time boxTime(String val, ZoneId zoneId) { return new Time(targetCal.getTimeInMillis()); } + /** + * Truncates a BigQuery timestamp string to 9 fractional digits (nanoseconds) because + * Instant.parse throws DateTimeParseException for >9 digits, and java.sql.Timestamp maxes out at + * nanos anyway. + */ + private static String truncateToNanoseconds(String iso) { + int dotIdx = iso.indexOf('.'); + // Fast path: if there is no dot or at most 9 fractional digits after the dot, return as-is. + if (dotIdx == -1 || iso.length() - dotIdx <= 10) { + return iso; + } + + int fractionEnd = dotIdx + 1; + while (fractionEnd < iso.length() && Character.isDigit(iso.charAt(fractionEnd))) { + fractionEnd++; + } + if (fractionEnd - dotIdx - 1 > 9) { + return iso.substring(0, dotIdx + 10) + iso.substring(fractionEnd); + } + return iso; + } + /** * Converts a BigQuery absolute TIMESTAMP string into a legacy Timestamp. Because it is absolute, * the Calendar timezone is explicitly ignored per JDBC 4.2 spec. @@ -105,11 +135,109 @@ public static Timestamp boxTimestamp(String val) { iso = iso.substring(0, 10) + 'T' + iso.substring(11); } + iso = truncateToNanoseconds(iso); + try { return Timestamp.from(Instant.parse(iso)); } catch (java.time.format.DateTimeParseException e) { // Fallback for non-standard formats - return Timestamp.valueOf(val); + return Timestamp.valueOf(truncateToNanoseconds(val)); + } + } + + /** + * Parses a numeric epoch decimal string (e.g. from BigQuery REST JSON) into a JSR-310 {@link + * Instant}. Sub-nanosecond precision is deterministically truncated (floor/down) rather than + * rounded to avoid boundary rollovers. + */ + public static Instant parseEpochDecimalToInstant(String epochDecimal) { + if (epochDecimal == null) { + return null; + } + BigDecimal bd = new BigDecimal(epochDecimal); + long seconds = bd.setScale(0, RoundingMode.FLOOR).longValue(); + long nanos = + bd.subtract(BigDecimal.valueOf(seconds)) + .movePointRight(9) + .setScale(0, RoundingMode.DOWN) + .longValue(); + return Instant.ofEpochSecond(seconds, nanos); + } + + /** + * Formats a numeric epoch decimal string into standard SQL timestamp string format ("yyyy-MM-dd + * HH:mm:ss.ffffff"). Sub-microsecond precision is deterministically truncated (down) to prevent + * timestamp boundary rollovers. + */ + public static String formatTimestampString(String epochDecimal) { + return formatTimestampString(epochDecimal, false); + } + + /** + * Formats a numeric epoch decimal string into standard SQL timestamp string format ("yyyy-MM-dd + * HH:mm:ss.ffffff[ffffff]"). Sub-microsecond / sub-picosecond precision is deterministically + * truncated (down) to prevent timestamp boundary rollovers. + */ + public static String formatTimestampString(String epochDecimal, boolean enableTimestampPicos) { + if (epochDecimal == null) { + return null; + } + + BigDecimal bd = new BigDecimal(epochDecimal); + long seconds = bd.setScale(0, RoundingMode.FLOOR).longValue(); + BigDecimal fractionalSeconds = bd.subtract(BigDecimal.valueOf(seconds)); + + int originalScale = bd.scale() > 0 ? bd.scale() : 0; + int scale = enableTimestampPicos ? Math.max(6, Math.min(12, originalScale)) : 6; + + String fraction = + fractionalSeconds.setScale(scale, RoundingMode.DOWN).toPlainString().substring(2); + + Instant instant = Instant.ofEpochSecond(seconds); + return UTC_FORMATTER.format(instant) + "." + fraction; + } + + public static String formatTimestampStringFromMicroseconds(long microseconds) { + long seconds = Math.floorDiv(microseconds, 1000000L); + long micros = Math.floorMod(microseconds, 1000000L); + + String fraction = Strings.padStart(Long.toString(micros), 6, '0'); + Instant instant = Instant.ofEpochSecond(seconds); + return UTC_FORMATTER.format(instant) + "." + fraction; + } + + public static String formatTimestampStringFromIso( + String isoString, boolean enableTimestampPicos) { + if (isoString == null) { + return null; + } + + String s = isoString; + if (s.endsWith(" UTC")) { + s = s.substring(0, s.length() - 4); + } else if (s.endsWith("Z")) { + s = s.substring(0, s.length() - 1); } + + if (s.length() > 10 && s.charAt(10) == 'T') { + s = s.substring(0, 10) + ' ' + s.substring(11); + } + + int dotIdx = s.indexOf('.'); + if (dotIdx == -1) { + return s + ".000000"; + } + + String base = s.substring(0, dotIdx); + String fraction = s.substring(dotIdx + 1); + + int maxScale = enableTimestampPicos ? 12 : 6; + if (fraction.length() > maxScale) { + fraction = fraction.substring(0, maxScale); + } else if (fraction.length() < 6) { + fraction = Strings.padEnd(fraction, 6, '0'); + } + + return base + "." + fraction; } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercionUtility.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercionUtility.java index aa21307db1ef..bc9a45a253bc 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercionUtility.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercionUtility.java @@ -182,6 +182,12 @@ static Timestamp convertTimestampWithCalendar(Timestamp timestamp, Calendar cal) LocalDateTime.class, Timestamp.class) .registerTypeCoercion(Text::toString, Text.class, String.class) + .registerTypeCoercion( + text -> BigQueryTemporalUtility.boxTimestamp(text.toString()), + Text.class, + Timestamp.class) + .registerTypeCoercion( + BigQueryTemporalUtility::boxTimestamp, String.class, Timestamp.class) .registerTypeCoercion(new TextToInteger()) .registerTypeCoercion(new LongToTimestamp()) .registerTypeCoercion(new LongToTime()) @@ -438,9 +444,9 @@ public Timestamp coerce(FieldValue fieldValue) { // Timestamp.valueOf() expects "yyyy-mm-dd hh:mm:ss.fffffffff" format. return Timestamp.valueOf(rawValue.replace('T', ' ')); } else { - // It's a TIMESTAMP numeric string. - long microseconds = fieldValue.getTimestampValue(); - Instant instant = Instant.EPOCH.plus(microseconds, ChronoUnit.MICROS); + // Numeric epoch decimal string from BigQuery JSON (e.g. "1775642400.123456789123" or + // "1.6905474E9") + Instant instant = BigQueryTemporalUtility.parseEpochDecimalToInstant(rawValue); // Timezone-agnostic conversion preserving exact point in time as mandated by JDBC spec return Timestamp.from(instant); } diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonArrayOfPrimitivesTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonArrayOfPrimitivesTest.java index 537e20b60fea..30a66a2bf5fd 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonArrayOfPrimitivesTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonArrayOfPrimitivesTest.java @@ -127,10 +127,10 @@ public static Collection data() { TIMESTAMP, arraySchemaAndValue( TIMESTAMP, - "1680174859.8202269", - "1680261259.8202269", - "1680347659.8202269", - "1680434059.8202269"), + "1680174859.820227", + "1680261259.820227", + "1680347659.820227", + "1680434059.820227"), new Timestamp[] { Timestamp.valueOf(aTimeStamp), // 2023-03-30 16:44:19.82 Timestamp.valueOf(aTimeStamp.plusDays(1)), diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSetTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSetTest.java index 06af37010d25..d95b39f065ce 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSetTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSetTest.java @@ -247,6 +247,8 @@ public void testIteration() throws SQLException { assertThat(bigQueryJsonResultSet.getFloat(3)).isEqualTo(1.5f); assertThat(bigQueryJsonResultSet.getString("fourth")).isEqualTo(STRING_VAL); assertThat(bigQueryJsonResultSet.getString(4)).isEqualTo(STRING_VAL); + assertThat(bigQueryJsonResultSet.getString("fifth")).isEqualTo("2023-03-30 11:14:19.820000"); + assertThat(bigQueryJsonResultSet.getString(5)).isEqualTo("2023-03-30 11:14:19.820000"); assertThat(bigQueryJsonResultSet.getTimestamp("fifth")) .isEqualTo(Timestamp.valueOf(aTimeStamp)); assertThat(bigQueryJsonResultSet.getTimestamp(5)).isEqualTo(Timestamp.valueOf(aTimeStamp)); @@ -506,6 +508,24 @@ public void testGetObjectWithType_failure(Object column, Class type) throws S } } + @Test + public void testGetString_timestamp() throws SQLException { + assertThat(resetResultSet()).isTrue(); + bigQueryJsonResultSet.next(); + assertThat(bigQueryJsonResultSet.getString("fifth")).isEqualTo("2023-03-30 11:14:19.820000"); + assertThat(bigQueryJsonResultSet.getString(5)).isEqualTo("2023-03-30 11:14:19.820000"); + } + + @Test + public void testGetString_structAndArray() throws SQLException { + assertThat(resetResultSet()).isTrue(); + bigQueryJsonResultSet.next(); + assertThat(bigQueryJsonResultSet.getString("eight")).isEqualTo("[10, 20]"); + assertThat(bigQueryJsonResultSet.getString(8)).isEqualTo("[10, 20]"); + assertThat(bigQueryJsonResultSet.getString("ninth")).isNotNull(); + assertThat(bigQueryJsonResultSet.getString(9)).isNotNull(); + } + private int resultSetRowCount(BigQueryJsonResultSet resultSet) throws SQLException { int rowCount = 0; while (resultSet.next()) { diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtilityTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtilityTest.java new file mode 100644 index 000000000000..3c58208e2a63 --- /dev/null +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtilityTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.bigquery.jdbc; + +import static com.google.common.truth.Truth.assertThat; + +import java.time.Instant; +import org.junit.jupiter.api.Test; + +public class BigQueryTemporalUtilityTest { + + @Test + public void testFormatTimestampString() { + assertThat(BigQueryTemporalUtility.formatTimestampString("1775642400.123456789", false)) + .isEqualTo("2026-04-08 10:00:00.123456"); + assertThat(BigQueryTemporalUtility.formatTimestampString("1775642400.123456789", true)) + .isEqualTo("2026-04-08 10:00:00.123456789"); + assertThat(BigQueryTemporalUtility.formatTimestampString("1775642400", false)) + .isEqualTo("2026-04-08 10:00:00.000000"); + assertThat(BigQueryTemporalUtility.formatTimestampString("0.123", false)) + .isEqualTo("1970-01-01 00:00:00.123000"); + assertThat(BigQueryTemporalUtility.formatTimestampString("-0.123456", false)) + .isEqualTo("1969-12-31 23:59:59.876544"); + assertThat(BigQueryTemporalUtility.formatTimestampString("-1.500000", false)) + .isEqualTo("1969-12-31 23:59:58.500000"); + assertThat(BigQueryTemporalUtility.formatTimestampString("-1.000000", false)) + .isEqualTo("1969-12-31 23:59:59.000000"); + assertThat(BigQueryTemporalUtility.formatTimestampString("-0.123456789123", true)) + .isEqualTo("1969-12-31 23:59:59.876543210877"); + assertThat(BigQueryTemporalUtility.formatTimestampString("1.6905474E9", false)) + .isEqualTo("2023-07-28 12:30:00.000000"); + assertThat(BigQueryTemporalUtility.formatTimestampString("1.690547400123456E9", true)) + .isEqualTo("2023-07-28 12:30:00.123456"); + } + + @Test + public void testFormatTimestampStringFromMicroseconds() { + assertThat(BigQueryTemporalUtility.formatTimestampStringFromMicroseconds(1775642400123456L)) + .isEqualTo("2026-04-08 10:00:00.123456"); + assertThat(BigQueryTemporalUtility.formatTimestampStringFromMicroseconds(-123456L)) + .isEqualTo("1969-12-31 23:59:59.876544"); + } + + @Test + public void testFormatTimestampStringFromIso() { + assertThat( + BigQueryTemporalUtility.formatTimestampStringFromIso( + "2026-04-08T10:00:00.123456789123Z", false)) + .isEqualTo("2026-04-08 10:00:00.123456"); + assertThat( + BigQueryTemporalUtility.formatTimestampStringFromIso( + "2026-04-08T10:00:00.123456789123Z", true)) + .isEqualTo("2026-04-08 10:00:00.123456789123"); + assertThat(BigQueryTemporalUtility.formatTimestampStringFromIso("2026-04-08T10:00:00Z", false)) + .isEqualTo("2026-04-08 10:00:00.000000"); + } + + @Test + public void testBoxTimestamp() { + // ISO format with UTC suffix and 12-digit picoseconds + java.sql.Timestamp tsUtc = + BigQueryTemporalUtility.boxTimestamp("2026-04-08 10:00:00.123456789123 UTC"); + assertThat(tsUtc.getNanos()).isEqualTo(123456789); + + // Fallback format (no timezone) with 12-digit picoseconds triggers Timestamp.valueOf fallback + java.sql.Timestamp tsFallback = + BigQueryTemporalUtility.boxTimestamp("2026-04-08 10:00:00.123456789123"); + assertThat(tsFallback.getNanos()).isEqualTo(123456789); + } + + @Test + public void testParseEpochDecimalToInstant() { + // Standard decimal + Instant i1 = BigQueryTemporalUtility.parseEpochDecimalToInstant("1775642400.123456789123"); + assertThat(i1).isEqualTo(Instant.ofEpochSecond(1775642400, 123456789)); + + // Scientific notation + Instant i2 = BigQueryTemporalUtility.parseEpochDecimalToInstant("1.6905474E9"); + assertThat(i2).isEqualTo(Instant.parse("2023-07-28T12:30:00Z")); + + // Pre-1970 negative decimal + Instant i3 = BigQueryTemporalUtility.parseEpochDecimalToInstant("-0.123456"); + assertThat(i3).isEqualTo(Instant.ofEpochSecond(-1, 876544000)); + + // Null + assertThat(BigQueryTemporalUtility.parseEpochDecimalToInstant(null)).isNull(); + } + + @Test + public void testTimestampTruncationNotRounding() { + // Values ending in .8202269 or .9999999 must truncate towards zero (DOWN), never round up + assertThat(BigQueryTemporalUtility.formatTimestampString("1680174859.8202269", false)) + .isEqualTo("2023-03-30 11:14:19.820226"); + assertThat(BigQueryTemporalUtility.formatTimestampString("1680174859.9999999", false)) + .isEqualTo("2023-03-30 11:14:19.999999"); + + Instant truncated = + BigQueryTemporalUtility.parseEpochDecimalToInstant("1680174859.820226999999"); + assertThat(truncated).isNotNull(); + assertThat(truncated.getNano()).isEqualTo(820226999); + } +} diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/FieldValueTypeBigQueryCoercionUtilityTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/FieldValueTypeBigQueryCoercionUtilityTest.java index 7b24e389f853..60661462f7bc 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/FieldValueTypeBigQueryCoercionUtilityTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/FieldValueTypeBigQueryCoercionUtilityTest.java @@ -306,6 +306,43 @@ public void fieldValueToTimestamp() { .isEqualTo(Timestamp.from(instant)); } + @Test + public void fieldValueToTimestampWithNanos() { + FieldValue picosValue = FieldValue.of(PRIMITIVE, "1775642400.123456789123"); + Timestamp result = INSTANCE.coerceTo(Timestamp.class, picosValue); + assertThat(result).isNotNull(); + assertThat(result.getNanos()).isEqualTo(123456789); + } + + @Test + public void fieldValueToTimestampScientificNotation() { + FieldValue scientificValue = FieldValue.of(PRIMITIVE, "1.6905474E9"); + Timestamp result = INSTANCE.coerceTo(Timestamp.class, scientificValue); + assertThat(result).isNotNull(); + assertThat(result).isEqualTo(Timestamp.valueOf("2023-07-28 12:30:00")); + } + + @Test + public void fieldValueToTimestampNegativeEpoch() { + FieldValue negativeOneAndHalf = FieldValue.of(PRIMITIVE, "-1.5"); + Timestamp result = INSTANCE.coerceTo(Timestamp.class, negativeOneAndHalf); + assertThat(result).isNotNull(); + assertThat(result).isEqualTo(Timestamp.from(Instant.ofEpochSecond(-2, 500000000))); + + FieldValue pre1970Nanos = FieldValue.of(PRIMITIVE, "-0.123456789"); + Timestamp resultNanos = INSTANCE.coerceTo(Timestamp.class, pre1970Nanos); + assertThat(resultNanos).isNotNull(); + assertThat(resultNanos).isEqualTo(Timestamp.from(Instant.ofEpochSecond(-1, 876543211))); + } + + @Test + public void fieldValueToTimestampTruncation() { + FieldValue val = FieldValue.of(PRIMITIVE, "1680174859.8202269"); + Timestamp ts = INSTANCE.coerceTo(Timestamp.class, val); + assertThat(ts).isNotNull(); + assertThat(ts.getNanos()).isEqualTo(820226900); + } + @Test public void fieldValueToTimestampWhenNull() { assertThat(INSTANCE.coerceTo(Timestamp.class, null)).isNull(); diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/utils/ArrowUtilities.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/utils/ArrowUtilities.java index 13f3007667d3..1af7cfec6b59 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/utils/ArrowUtilities.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/utils/ArrowUtilities.java @@ -41,8 +41,9 @@ public static ByteString serializeSchema(Schema schema) throws IOException { public static ByteString serializeVectorSchemaRoot(VectorSchemaRoot root) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); - ArrowRecordBatch recordBatch = new VectorUnloader(root).getRecordBatch(); - MessageSerializer.serialize(new WriteChannel(Channels.newChannel(out)), recordBatch); + try (ArrowRecordBatch recordBatch = new VectorUnloader(root).getRecordBatch()) { + MessageSerializer.serialize(new WriteChannel(Channels.newChannel(out)), recordBatch); + } return ByteString.readFrom(new ByteArrayInputStream(out.toByteArray())); // ArrowStreamWriter writer = new ArrowStreamWriter(root, null, Channels.newChannel(out));