diff --git a/c/pom.xml b/c/pom.xml
index c90b6dc0ef..27b6619c4c 100644
--- a/c/pom.xml
+++ b/c/pom.xml
@@ -22,7 +22,7 @@ under the License.
org.apache.arrow
arrow-java-root
- 19.0.0-SNAPSHOT
+ 20.0.0-SNAPSHOT
arrow-c-data
diff --git a/c/src/main/java/org/apache/arrow/c/ReferenceCountedArrowArray.java b/c/src/main/java/org/apache/arrow/c/ReferenceCountedArrowArray.java
index cf50f9417b..f51fb25105 100644
--- a/c/src/main/java/org/apache/arrow/c/ReferenceCountedArrowArray.java
+++ b/c/src/main/java/org/apache/arrow/c/ReferenceCountedArrowArray.java
@@ -64,13 +64,18 @@ void release() {
*/
ArrowBuf unsafeAssociateAllocation(
BufferAllocator trackingAllocator, long capacity, long memoryAddress) {
+ // Retain only after wrapForeignAllocation succeeds. On the allocator-limit OOM path,
+ // wrapForeignAllocation throws before the ForeignAllocation is associated, so release0()
+ // is not called; retaining first would leave the count elevated with no matching release0().
+ ArrowBuf buf =
+ trackingAllocator.wrapForeignAllocation(
+ new ForeignAllocation(capacity, memoryAddress) {
+ @Override
+ protected void release0() {
+ ReferenceCountedArrowArray.this.release();
+ }
+ });
retain();
- return trackingAllocator.wrapForeignAllocation(
- new ForeignAllocation(capacity, memoryAddress) {
- @Override
- protected void release0() {
- ReferenceCountedArrowArray.this.release();
- }
- });
+ return buf;
}
}
diff --git a/c/src/test/java/org/apache/arrow/c/ImportOutOfMemoryTest.java b/c/src/test/java/org/apache/arrow/c/ImportOutOfMemoryTest.java
new file mode 100644
index 0000000000..7c099f2ef0
--- /dev/null
+++ b/c/src/test/java/org/apache/arrow/c/ImportOutOfMemoryTest.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.arrow.c;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.OutOfMemoryException;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.vector.FieldVector;
+import org.apache.arrow.vector.VarCharVector;
+import org.apache.arrow.vector.VectorSchemaRoot;
+import org.apache.arrow.vector.types.pojo.Schema;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Regression test: a mid-import {@link OutOfMemoryException} must not leak the imported array.
+ *
+ * A "producer" allocator owns the exported batch; if the C Data release callback fires, the
+ * producer drains to zero. A too-small consumer allocator forces an OOM part-way through the
+ * import. The test asserts the producer drains, confirming the release callback fired despite the
+ * failure.
+ */
+final class ImportOutOfMemoryTest {
+ private static final int ROWS = 1024;
+ private static final int VALUE_BYTES = 256;
+ private static final int COLUMNS = 4;
+ // Far smaller than the exported batch, so the import OOMs part-way through the buffers.
+ private static final long TINY_LIMIT = 16 * 1024;
+
+ private RootAllocator root;
+
+ @BeforeEach
+ public void setUp() {
+ root = new RootAllocator(Long.MAX_VALUE);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ root.close();
+ }
+
+ @Test
+ public void importOomDoesNotLeakExportedArray() {
+ // "producer" owns only the exported batch buffers; the C Data struct containers live on a
+ // separate allocator (they are consumed/closed by import, which would otherwise muddy the
+ // producer's balance). So producer draining to zero is an exact signal that the array's release
+ // callback fired.
+ try (BufferAllocator producer = root.newChildAllocator("producer", 0, Long.MAX_VALUE);
+ BufferAllocator structs = root.newChildAllocator("structs", 0, Long.MAX_VALUE)) {
+ try (ArrowArray array = ArrowArray.allocateNew(structs);
+ ArrowSchema schema = ArrowSchema.allocateNew(structs)) {
+ exportBatch(producer, array, schema);
+ assertTrue(
+ producer.getAllocatedMemory() > 0, "producer holds the exported batch before import");
+
+ // A consumer allocator far too small to hold the batch: the import throws part-way through.
+ try (BufferAllocator consumer = root.newChildAllocator("consumer", 0, TINY_LIMIT);
+ CDataDictionaryProvider provider = new CDataDictionaryProvider()) {
+ Schema importSchema = Data.importSchema(consumer, schema, provider);
+ try (VectorSchemaRoot importRoot = VectorSchemaRoot.create(importSchema, consumer)) {
+ Exception thrown =
+ assertThrows(
+ Exception.class,
+ () -> Data.importIntoVectorSchemaRoot(consumer, array, importRoot, provider));
+ assertTrue(
+ hasOutOfMemoryCause(thrown),
+ "mid-import failure must be an allocator OOM: " + thrown);
+ }
+ }
+
+ // The array's release callback must have fired despite the mid-import OOM, freeing the
+ // whole exported batch. On the unfixed retain-before-wrap code the batch is stranded.
+ assertEquals(
+ 0L,
+ producer.getAllocatedMemory(),
+ "import OOM leaked the exported batch (producer not drained)");
+ }
+ }
+ }
+
+ /** True if {@code t} is, or is caused by, an Arrow {@link OutOfMemoryException}. */
+ private static boolean hasOutOfMemoryCause(Throwable t) {
+ for (Throwable cause = t; cause != null; cause = cause.getCause()) {
+ if (cause instanceof OutOfMemoryException) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Builds a wide multi-column VarChar batch on {@code alloc} and exports it into the C structs.
+ */
+ private void exportBatch(BufferAllocator alloc, ArrowArray array, ArrowSchema schema) {
+ byte[] value = new byte[VALUE_BYTES];
+ for (int i = 0; i < value.length; i++) {
+ value[i] = (byte) 'x';
+ }
+ List vectors = new ArrayList<>(COLUMNS);
+ for (int c = 0; c < COLUMNS; c++) {
+ VarCharVector vector = new VarCharVector("col" + c, alloc);
+ vector.allocateNew((long) ROWS * VALUE_BYTES, ROWS);
+ for (int r = 0; r < ROWS; r++) {
+ vector.setSafe(r, value);
+ }
+ vector.setValueCount(ROWS);
+ vectors.add(vector);
+ }
+ try (VectorSchemaRoot source = new VectorSchemaRoot(vectors)) {
+ long total = 0;
+ for (FieldVector vector : source.getFieldVectors()) {
+ total += vector.getBufferSize();
+ }
+ assertTrue(total > TINY_LIMIT, "test setup: batch must exceed the consumer limit");
+ Data.exportVectorSchemaRoot(alloc, source, null, array, schema);
+ }
+ }
+}
diff --git a/ci/docker/conda-jni.dockerfile b/ci/docker/conda-jni.dockerfile
index e14db73688..3f31b74052 100644
--- a/ci/docker/conda-jni.dockerfile
+++ b/ci/docker/conda-jni.dockerfile
@@ -17,7 +17,7 @@
FROM ghcr.io/mamba-org/micromamba:ubuntu24.04
-ARG jdk=11
+ARG jdk=17
ARG maven=3.9.9
RUN micromamba install -y \
diff --git a/ci/docker/vcpkg-jni.dockerfile b/ci/docker/vcpkg-jni.dockerfile
index d6bd322a39..f2f5d0d45a 100644
--- a/ci/docker/vcpkg-jni.dockerfile
+++ b/ci/docker/vcpkg-jni.dockerfile
@@ -20,7 +20,7 @@ FROM ${base}
# Install Java
# We need Java for JNI headers, but we don't invoke Maven in this build.
-ARG java=11
+ARG java=17
RUN dnf install -y java-$java-openjdk-devel && dnf clean all
# For ci/scripts/{cpp,java}_*.sh
diff --git a/ci/scripts/test.sh b/ci/scripts/test.sh
index cacc20034e..8061ee455d 100755
--- a/ci/scripts/test.sh
+++ b/ci/scripts/test.sh
@@ -34,10 +34,11 @@ fi
mvn="mvn -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"
# Use `2 * ncores` threads
mvn="${mvn} -T 2C"
+mvn="${mvn} -Denforcer.skip=true"
pushd "${build_dir}"
-${mvn} -Darrow.test.dataRoot="${source_dir}/testing/data" clean test
+${mvn} -Darrow.test.dataRoot="${source_dir}/testing/data" test
projects=()
if [ "${ARROW_JAVA_JNI}" = "ON" ]; then
@@ -46,7 +47,7 @@ if [ "${ARROW_JAVA_JNI}" = "ON" ]; then
projects+=(gandiva)
fi
if [ "${#projects[@]}" -gt 0 ]; then
- ${mvn} clean test \
+ ${mvn} test \
-Parrow-jni \
-pl "$(
IFS=,
@@ -56,7 +57,7 @@ if [ "${#projects[@]}" -gt 0 ]; then
fi
if [ "${ARROW_JAVA_CDATA}" = "ON" ]; then
- ${mvn} clean test -Parrow-c-data -pl c -Darrow.c.jni.dist.dir="${java_jni_dist_dir}"
+ ${mvn} test -Parrow-c-data -pl c -Darrow.c.jni.dist.dir="${java_jni_dist_dir}"
fi
popd
diff --git a/compose.yaml b/compose.yaml
index f5082a22aa..4fd825e5a5 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -40,7 +40,7 @@ services:
# docker compose run ubuntu
# Parameters:
# MAVEN: 3.9.9
- # JDK: 11, 17, 21
+ # JDK: 17, 21
image: ${ARCH}/maven:${MAVEN}-eclipse-temurin-${JDK}
volumes:
- .:/arrow-java:delegated
@@ -60,7 +60,7 @@ services:
# docker compose run conda-jni-cdata
# Parameters:
# MAVEN: 3.9.9
- # JDK: 11, 17, 21
+ # JDK: 17, 21
image: ${REPO}:${ARCH}-conda-java-${JDK}-maven-${MAVEN}-jni-integration
build:
context: .
@@ -109,5 +109,6 @@ services:
ARROW_JAVA_CDATA: "ON"
CCACHE_DIR: "/ccache"
command:
- ["git config --global --add safe.directory /arrow-java && \
+ ["/bin/bash", "-c",
+ "git config --global --add safe.directory /arrow-java &&
/arrow-java/ci/scripts/jni_manylinux_build.sh /arrow-java /arrow /build/java /arrow-java/jni"]
diff --git a/compression/pom.xml b/compression/pom.xml
index 29f8b41788..aa7dee6f89 100644
--- a/compression/pom.xml
+++ b/compression/pom.xml
@@ -22,7 +22,7 @@ under the License.
org.apache.arrow
arrow-java-root
- 19.0.0-SNAPSHOT
+ 20.0.0-SNAPSHOT
arrow-compression
Arrow Compression
@@ -55,7 +55,7 @@ under the License.
com.github.luben
zstd-jni
- 1.5.7-6
+ 1.5.7-11
diff --git a/compression/src/main/java/org/apache/arrow/compression/Lz4CompressionCodec.java b/compression/src/main/java/org/apache/arrow/compression/Lz4CompressionCodec.java
index 91cefc2a9e..f268e815fe 100644
--- a/compression/src/main/java/org/apache/arrow/compression/Lz4CompressionCodec.java
+++ b/compression/src/main/java/org/apache/arrow/compression/Lz4CompressionCodec.java
@@ -80,6 +80,13 @@ protected ArrowBuf doDecompress(BufferAllocator allocator, ArrowBuf compressedBu
}
byte[] outBytes = out.toByteArray();
+ if (outBytes.length != decompressedLength) {
+ throw new RuntimeException(
+ "Expected != actual decompressed length: "
+ + decompressedLength
+ + " != "
+ + outBytes.length);
+ }
ArrowBuf decompressedBuffer = allocator.buffer(outBytes.length);
decompressedBuffer.setBytes(/* index= */ 0, outBytes);
decompressedBuffer.writerIndex(decompressedLength);
diff --git a/compression/src/main/java/org/apache/arrow/compression/ZstdCompressionCodec.java b/compression/src/main/java/org/apache/arrow/compression/ZstdCompressionCodec.java
index 290723608d..ed46fe81b4 100644
--- a/compression/src/main/java/org/apache/arrow/compression/ZstdCompressionCodec.java
+++ b/compression/src/main/java/org/apache/arrow/compression/ZstdCompressionCodec.java
@@ -44,7 +44,7 @@ protected ArrowBuf doCompress(BufferAllocator allocator, ArrowBuf uncompressedBu
long bytesWritten =
Zstd.compressUnsafe(
compressedBuffer.memoryAddress() + CompressionUtil.SIZE_OF_UNCOMPRESSED_LENGTH,
- dstSize,
+ maxSize,
/*src*/ uncompressedBuffer.memoryAddress(),
/* srcSize= */ uncompressedBuffer.writerIndex(),
/* level= */ this.compressionLevel);
diff --git a/compression/src/test/java/org/apache/arrow/compression/TestCompressionCodec.java b/compression/src/test/java/org/apache/arrow/compression/TestCompressionCodec.java
index b8fb4e28b9..d2d2921649 100644
--- a/compression/src/test/java/org/apache/arrow/compression/TestCompressionCodec.java
+++ b/compression/src/test/java/org/apache/arrow/compression/TestCompressionCodec.java
@@ -20,6 +20,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream;
@@ -59,6 +60,7 @@
import org.apache.arrow.vector.util.ByteArrayReadableSeekableByteChannel;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -231,6 +233,26 @@ void testEmptyBuffer(int vectorLength, CompressionCodec codec) throws Exception
AutoCloseables.close(decompressedBuffers);
}
+ @Test
+ void testLz4DecompressRejectsWrongLength() {
+ byte[] data = new byte[512]; // all zeros, highly compressible
+ ArrowBuf orig = allocator.buffer(data.length);
+ orig.setBytes(0, data);
+ orig.writerIndex(data.length);
+
+ CompressionCodec codec = new Lz4CompressionCodec();
+ ArrowBuf compressed = codec.compress(allocator, orig);
+
+ // tamper with the 8-byte uncompressed-length prefix so it no longer matches
+ // the real decompressed size
+ compressed.setLong(0, 1_000_000L);
+
+ RuntimeException e =
+ assertThrows(RuntimeException.class, () -> codec.decompress(allocator, compressed));
+ assertTrue(e.getMessage().contains("decompressed length"));
+ compressed.close();
+ }
+
private static Stream codecTypes() {
return Arrays.stream(CompressionUtil.CodecType.values());
}
diff --git a/dataset/pom.xml b/dataset/pom.xml
index 686a234358..5acc837860 100644
--- a/dataset/pom.xml
+++ b/dataset/pom.xml
@@ -22,7 +22,7 @@ under the License.
org.apache.arrow
arrow-java-root
- 19.0.0-SNAPSHOT
+ 20.0.0-SNAPSHOT
arrow-dataset
@@ -32,7 +32,7 @@ under the License.
../../../cpp/release-build/
- 1.17.0
+ 1.17.1
1.12.1
@@ -130,7 +130,7 @@ under the License.
org.apache.orc
orc-core
- 2.2.2
+ 2.3.0
test
@@ -156,7 +156,7 @@ under the License.
commons-io
commons-io
- 2.21.0
+ 2.22.0
test
diff --git a/dev/release/bump_version.sh b/dev/release/bump_version.sh
index 458e930f98..68cafb99bd 100755
--- a/dev/release/bump_version.sh
+++ b/dev/release/bump_version.sh
@@ -37,6 +37,7 @@ if [ ! -f "${SOURCE_DIR}/.env" ]; then
exit 1
fi
. "${SOURCE_DIR}/.env"
+export GH_TOKEN
cd "${SOURCE_TOP_DIR}"
diff --git a/dev/release/release.sh b/dev/release/release.sh
index f08a618c4f..d1db7ad05a 100755
--- a/dev/release/release.sh
+++ b/dev/release/release.sh
@@ -36,6 +36,7 @@ if [ ! -f "${SOURCE_DIR}/.env" ]; then
exit 1
fi
. "${SOURCE_DIR}/.env"
+export GH_TOKEN
git_origin_url="$(git remote get-url origin)"
repository="${git_origin_url#*github.com?}"
diff --git a/dev/release/release_rc.sh b/dev/release/release_rc.sh
index ff77718b8d..0920edbe35 100755
--- a/dev/release/release_rc.sh
+++ b/dev/release/release_rc.sh
@@ -42,6 +42,7 @@ if [ ! -f "${SOURCE_DIR}/.env" ]; then
exit 1
fi
. "${SOURCE_DIR}/.env"
+export GH_TOKEN
cd "${SOURCE_TOP_DIR}"
diff --git a/docs/source/cdata.rst b/docs/source/cdata.rst
index 9643d88df3..7b2924d259 100644
--- a/docs/source/cdata.rst
+++ b/docs/source/cdata.rst
@@ -101,8 +101,8 @@ without writing JNI bindings ourselves.
1.0-SNAPSHOT
- 8
- 8
+ 17
+ 17
9.0.0
@@ -237,8 +237,8 @@ For this example, we will build a JAR with all dependencies bundled.
cpptojava
1.0-SNAPSHOT
- 8
- 8
+ 17
+ 17
9.0.0
diff --git a/docs/source/developers/building.rst b/docs/source/developers/building.rst
index f9ef7daea8..b682957714 100644
--- a/docs/source/developers/building.rst
+++ b/docs/source/developers/building.rst
@@ -32,7 +32,7 @@ Arrow Java uses the `Maven `_ build system.
Building requires:
-* JDK 11+
+* JDK 17+
* Maven 3+
.. note::
@@ -345,7 +345,7 @@ configuration file usually located under ``${HOME}/.m2`` with the following snip
jdk
- 21
+ 21
temurin
@@ -383,11 +383,11 @@ Arrow repository, and update the following settings:
right click the directory, and select Mark Directory as > Generated Sources
Root. There is no need to mark other generated sources directories, as only
the ``vector`` module generates sources.
-* For JDK 11, due to an `IntelliJ bug
- `__, you must go into
+* Due to an `IntelliJ bug
+ `__, you may need to go into
Settings > Build, Execution, Deployment > Compiler > Java Compiler and disable
"Use '--release' option for cross-compilation (Java 9 and later)". Otherwise
- you will get an error like "package sun.misc does not exist".
+ you may get an error like "package sun.misc does not exist".
* You may want to disable error-prone entirely if it gives spurious
warnings (disable both error-prone profiles in the Maven tool window
and "Reload All Maven Projects").
@@ -397,7 +397,7 @@ Arrow repository, and update the following settings:
* To enable debugging JNI-based modules like ``dataset``,
activate specific profiles in the Maven tab under "Profiles".
Ensure the profiles ``arrow-c-data``, ``arrow-jni``, ``generate-libs-cdata-all-os``,
- ``generate-libs-jni-macos-linux``, and ``jdk11+`` are enabled, so that the
+ ``generate-libs-jni-macos-linux``, and ``jdk17+`` are enabled, so that the
IDE can build them and enable debugging.
You may not need to update all of these settings if you build/test with the
@@ -478,8 +478,8 @@ Installing Manually
.. code-block:: xml
- 8
- 8
+ 17
+ 17
9.0.0.dev501
diff --git a/docs/source/flight_sql_jdbc_driver.rst b/docs/source/flight_sql_jdbc_driver.rst
index 4deb726b33..6d40434a22 100644
--- a/docs/source/flight_sql_jdbc_driver.rst
+++ b/docs/source/flight_sql_jdbc_driver.rst
@@ -27,7 +27,7 @@ Flight SQL.
Installation and Requirements
=============================
-The driver is compatible with JDK 11+. Note that the following JVM
+The driver is compatible with JDK 17+. Note that the following JVM
parameter is required:
.. code-block:: shell
diff --git a/docs/source/install.rst b/docs/source/install.rst
index b2b1c7163f..e0b34515ef 100644
--- a/docs/source/install.rst
+++ b/docs/source/install.rst
@@ -27,8 +27,8 @@ Java modules are regularly built and tested on macOS and Linux distributions.
Java Compatibility
==================
-Java modules are compatible with JDK 11 and above. Currently, JDK versions
-11, 17, 21, and latest are tested in CI.
+Java modules are compatible with JDK 17 and above. Currently, JDK versions
+17, 21, and latest are tested in CI.
Note that some JDK internals must be exposed by
adding ``--add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED`` to the ``java`` command:
diff --git a/docs/source/jdbc.rst b/docs/source/jdbc.rst
index 2f57c34bf8..e054127faa 100644
--- a/docs/source/jdbc.rst
+++ b/docs/source/jdbc.rst
@@ -276,7 +276,7 @@ mapping, with additional support for the UUID extension type noted below.
JDBC value, because a JDBC Timestamp is in UTC, and we have no
timezone information. In this case, the default binder will call
`setTimestamp(int, Timestamp)
- `_,
+ `_,
which will lead to the driver using the "default timezone" (that of
the Java VM).
* \(3) For the Flight SQL JDBC driver, the Arrow UUID extension type
diff --git a/docs/source/memory.rst b/docs/source/memory.rst
index 4a71ed846a..5b9148223a 100644
--- a/docs/source/memory.rst
+++ b/docs/source/memory.rst
@@ -341,7 +341,7 @@ How this works:
.. _`newChildAllocator`: https://arrow.apache.org/java/current/reference/org.apache.arrow.memory.core/org/apache/arrow/memory/RootAllocator.html#newChildAllocator-java.lang.String-org.apache.arrow.memory.AllocationListener-long-long-
.. _`Netty`: https://netty.io/wiki/
.. _`sun.misc.unsafe`: https://web.archive.org/web/20210929024401/http://www.docjar.com/html/api/sun/misc/Unsafe.java.html
-.. _`Direct Memory`: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/ByteBuffer.html
+.. _`Direct Memory`: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/nio/ByteBuffer.html
.. _`ReferenceManager`: https://arrow.apache.org/java/current/reference/org.apache.arrow.memory.core/org/apache/arrow/memory/ReferenceManager.html
.. _`ReferenceManager.release`: https://arrow.apache.org/java/current/reference/org.apache.arrow.memory.core/org/apache/arrow/memory/ReferenceManager.html#release--
.. _`ReferenceManager.retain`: https://arrow.apache.org/java/current/reference/org.apache.arrow.memory.core/org/apache/arrow/memory/ReferenceManager.html#retain--
diff --git a/docs/source/overview.rst b/docs/source/overview.rst
index be579c1495..1188054114 100644
--- a/docs/source/overview.rst
+++ b/docs/source/overview.rst
@@ -45,6 +45,9 @@ but some modules are JNI bindings to the C++ library.
* - arrow-vector
- An off-heap reference implementation for Arrow columnar data format.
- Native
+ * - arrow-vector-codegen
+ - Template files for Arrow datatypes suitable for code generation.
+ - Native
* - arrow-tools
- Java applications for working with Arrow ValueVectors.
- Native
diff --git a/flight/flight-core/pom.xml b/flight/flight-core/pom.xml
index b1f755844e..9ae402cdc4 100644
--- a/flight/flight-core/pom.xml
+++ b/flight/flight-core/pom.xml
@@ -22,7 +22,7 @@ under the License.
org.apache.arrow
arrow-flight
- 19.0.0-SNAPSHOT
+ 20.0.0-SNAPSHOT
flight-core
@@ -134,7 +134,7 @@ under the License.
com.google.api.grpc
proto-google-common-protos
- 2.63.2
+ 2.72.0
test
diff --git a/flight/flight-core/src/main/java/org/apache/arrow/flight/CallHeaders.java b/flight/flight-core/src/main/java/org/apache/arrow/flight/CallHeaders.java
index f4f6486a3c..0939d232cf 100644
--- a/flight/flight-core/src/main/java/org/apache/arrow/flight/CallHeaders.java
+++ b/flight/flight-core/src/main/java/org/apache/arrow/flight/CallHeaders.java
@@ -26,10 +26,20 @@ public interface CallHeaders {
/** Get the value of a metadata key. If multiple values are present, then get the last one. */
byte[] getByte(String key);
- /** Get all values present for the given metadata key. */
+ /**
+ * Get all values present for the given metadata key.
+ *
+ * @param key the metadata key
+ * @return an iterable of all values for the key. Returns an empty iterable if no value to return.
+ */
Iterable getAll(String key);
- /** Get all values present for the given metadata key. */
+ /**
+ * Get all values present for the given metadata key.
+ *
+ * @param key the metadata key
+ * @return an iterable of all values for the key. Returns an empty iterable if no value to return.
+ */
Iterable getAllByte(String key);
/**
diff --git a/flight/flight-core/src/main/java/org/apache/arrow/flight/ServerSessionMiddleware.java b/flight/flight-core/src/main/java/org/apache/arrow/flight/ServerSessionMiddleware.java
index 47fd6f1366..5ec01b9c83 100644
--- a/flight/flight-core/src/main/java/org/apache/arrow/flight/ServerSessionMiddleware.java
+++ b/flight/flight-core/src/main/java/org/apache/arrow/flight/ServerSessionMiddleware.java
@@ -80,20 +80,18 @@ public ServerSessionMiddleware onCallStarted(
String sessionId = null;
final Iterable it = incomingHeaders.getAll("cookie");
- if (it != null) {
- findIdCookie:
- for (final String headerValue : it) {
- for (final String cookie : headerValue.split(" ;")) {
- final String[] cookiePair = cookie.split("=");
- if (cookiePair.length != 2) {
- // Soft failure: Ignore invalid cookie list field
- break;
- }
-
- if (sessionCookieName.equals(cookiePair[0]) && cookiePair[1].length() > 0) {
- sessionId = cookiePair[1];
- break findIdCookie;
- }
+ findIdCookie:
+ for (final String headerValue : it) {
+ for (final String cookie : headerValue.split(" ;")) {
+ final String[] cookiePair = cookie.split("=");
+ if (cookiePair.length != 2) {
+ // Soft failure: Ignore invalid cookie list field
+ break;
+ }
+
+ if (sessionCookieName.equals(cookiePair[0]) && cookiePair[1].length() > 0) {
+ sessionId = cookiePair[1];
+ break findIdCookie;
}
}
}
diff --git a/flight/flight-core/src/main/java/org/apache/arrow/flight/client/ClientCookieMiddleware.java b/flight/flight-core/src/main/java/org/apache/arrow/flight/client/ClientCookieMiddleware.java
index e5eb934001..b33e6b7ecc 100644
--- a/flight/flight-core/src/main/java/org/apache/arrow/flight/client/ClientCookieMiddleware.java
+++ b/flight/flight-core/src/main/java/org/apache/arrow/flight/client/ClientCookieMiddleware.java
@@ -100,10 +100,7 @@ public void onBeforeSendingHeaders(CallHeaders outgoingHeaders) {
@Override
public void onHeadersReceived(CallHeaders incomingHeaders) {
- final Iterable setCookieHeaders = incomingHeaders.getAll(SET_COOKIE_HEADER);
- if (setCookieHeaders != null) {
- factory.updateCookies(setCookieHeaders);
- }
+ factory.updateCookies(incomingHeaders.getAll(SET_COOKIE_HEADER));
}
@Override
diff --git a/flight/flight-core/src/main/java/org/apache/arrow/flight/grpc/GetReadableBuffer.java b/flight/flight-core/src/main/java/org/apache/arrow/flight/grpc/GetReadableBuffer.java
index 45c32a86c6..fcba88d212 100644
--- a/flight/flight-core/src/main/java/org/apache/arrow/flight/grpc/GetReadableBuffer.java
+++ b/flight/flight-core/src/main/java/org/apache/arrow/flight/grpc/GetReadableBuffer.java
@@ -87,13 +87,13 @@ public static void readIntoBuffer(
final InputStream stream, final ArrowBuf buf, final int size, final boolean fastPath)
throws IOException {
ReadableBuffer readableBuffer = fastPath ? getReadableBuffer(stream) : null;
+ byte[] heapBytes = new byte[size];
if (readableBuffer != null) {
- readableBuffer.readBytes(buf.nioBuffer(0, size));
+ readableBuffer.readBytes(heapBytes, 0, size);
} else {
- byte[] heapBytes = new byte[size];
ByteStreams.readFully(stream, heapBytes);
- buf.writeBytes(heapBytes);
}
+ buf.writeBytes(heapBytes);
buf.writerIndex(size);
}
}
diff --git a/flight/flight-core/src/main/java/org/apache/arrow/flight/grpc/MetadataAdapter.java b/flight/flight-core/src/main/java/org/apache/arrow/flight/grpc/MetadataAdapter.java
index a1de16ede6..64a0769d63 100644
--- a/flight/flight-core/src/main/java/org/apache/arrow/flight/grpc/MetadataAdapter.java
+++ b/flight/flight-core/src/main/java/org/apache/arrow/flight/grpc/MetadataAdapter.java
@@ -18,6 +18,7 @@
import io.grpc.Metadata;
import java.nio.charset.StandardCharsets;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
@@ -53,13 +54,17 @@ public byte[] getByte(String key) {
@Override
public Iterable getAll(String key) {
- return this.metadata.getAll(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER));
+ final Iterable all =
+ this.metadata.getAll(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER));
+ return all != null ? all : Collections.emptyList();
}
@Override
public Iterable getAllByte(String key) {
if (key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
- return this.metadata.getAll(Metadata.Key.of(key, Metadata.BINARY_BYTE_MARSHALLER));
+ final Iterable all =
+ this.metadata.getAll(Metadata.Key.of(key, Metadata.BINARY_BYTE_MARSHALLER));
+ return all != null ? all : Collections.emptyList();
}
return StreamSupport.stream(getAll(key).spliterator(), false)
.map(String::getBytes)
diff --git a/flight/flight-core/src/test/java/org/apache/arrow/flight/TestCallOptions.java b/flight/flight-core/src/test/java/org/apache/arrow/flight/TestCallOptions.java
index a54ce69812..8aef9c69a1 100644
--- a/flight/flight-core/src/test/java/org/apache/arrow/flight/TestCallOptions.java
+++ b/flight/flight-core/src/test/java/org/apache/arrow/flight/TestCallOptions.java
@@ -21,6 +21,7 @@
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -110,6 +111,16 @@ public void mixedProperties() {
testHeaders(headers);
}
+ @Test
+ public void getAllReturnsEmptyIterableForMissingKey() {
+ FlightCallHeaders headers = new FlightCallHeaders();
+
+ assertNotNull(headers.getAll("missing"));
+ assertFalse(headers.getAll("missing").iterator().hasNext());
+ assertNotNull(headers.getAllByte("missing-bin"));
+ assertFalse(headers.getAllByte("missing-bin").iterator().hasNext());
+ }
+
private void testHeaders(CallHeaders headers) {
try (BufferAllocator a = new RootAllocator(Long.MAX_VALUE);
HeaderProducer producer = new HeaderProducer();
diff --git a/flight/flight-core/src/test/java/org/apache/arrow/flight/TestErrorMetadata.java b/flight/flight-core/src/test/java/org/apache/arrow/flight/TestErrorMetadata.java
index a9a3e355bc..214614defd 100644
--- a/flight/flight-core/src/test/java/org/apache/arrow/flight/TestErrorMetadata.java
+++ b/flight/flight-core/src/test/java/org/apache/arrow/flight/TestErrorMetadata.java
@@ -20,6 +20,7 @@
import static org.apache.arrow.flight.Location.forGrpcInsecure;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -119,6 +120,16 @@ public void testFlightMetadata() throws Exception {
}
}
+ @Test
+ public void getAllReturnsEmptyIterableForMissingKey() {
+ ErrorFlightMetadata metadata = new ErrorFlightMetadata();
+
+ assertNotNull(metadata.getAll("missing"));
+ assertFalse(metadata.getAll("missing").iterator().hasNext());
+ assertNotNull(metadata.getAllByte("missing-bin"));
+ assertFalse(metadata.getAllByte("missing-bin").iterator().hasNext());
+ }
+
private static class StatusRuntimeExceptionProducer extends NoOpFlightProducer {
private final PerfOuterClass.Perf perf;
diff --git a/flight/flight-core/src/test/java/org/apache/arrow/flight/auth/TestBasicAuth.java b/flight/flight-core/src/test/java/org/apache/arrow/flight/auth/TestBasicAuth.java
index 0c63785c88..0f202ba2d9 100644
--- a/flight/flight-core/src/test/java/org/apache/arrow/flight/auth/TestBasicAuth.java
+++ b/flight/flight-core/src/test/java/org/apache/arrow/flight/auth/TestBasicAuth.java
@@ -178,6 +178,12 @@ public static void shutdown() throws Exception {
AutoCloseables.close(server);
allocator.getChildAllocators().forEach(BufferAllocator::close);
+
+ // gRPC/Netty may still be releasing Arrow buffers asynchronously after server shutdown.
+ // Poll briefly to allow in-flight buffer releases to complete before closing the allocator.
+ for (int i = 0; i < 20 && allocator.getAllocatedMemory() > 0; i++) {
+ Thread.sleep(100);
+ }
AutoCloseables.close(allocator);
}
}
diff --git a/flight/flight-core/src/test/java/org/apache/arrow/flight/grpc/TestMetadataAdapter.java b/flight/flight-core/src/test/java/org/apache/arrow/flight/grpc/TestMetadataAdapter.java
new file mode 100644
index 0000000000..b0f5dcfcfc
--- /dev/null
+++ b/flight/flight-core/src/test/java/org/apache/arrow/flight/grpc/TestMetadataAdapter.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.arrow.flight.grpc;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import io.grpc.Metadata;
+import org.junit.jupiter.api.Test;
+
+public class TestMetadataAdapter {
+
+ @Test
+ public void getAllReturnsEmptyIterableForMissingKey() {
+ MetadataAdapter headers = new MetadataAdapter(new Metadata());
+
+ assertNotNull(headers.getAll("missing"));
+ assertFalse(headers.getAll("missing").iterator().hasNext());
+ assertNotNull(headers.getAllByte("missing-bin"));
+ assertFalse(headers.getAllByte("missing-bin").iterator().hasNext());
+ }
+}
diff --git a/flight/flight-integration-tests/pom.xml b/flight/flight-integration-tests/pom.xml
index 78a2d08ee1..f6ae8e16a5 100644
--- a/flight/flight-integration-tests/pom.xml
+++ b/flight/flight-integration-tests/pom.xml
@@ -22,7 +22,7 @@ under the License.
org.apache.arrow
arrow-flight
- 19.0.0-SNAPSHOT
+ 20.0.0-SNAPSHOT
flight-integration-tests
@@ -58,7 +58,7 @@ under the License.
commons-cli
commons-cli
- 1.9.0
+ 1.11.0
org.slf4j
@@ -101,7 +101,7 @@ under the License.
-
+
META-INF/LICENSE.txt
src/shade/LICENSE.txt
diff --git a/flight/flight-sql-jdbc-core/pom.xml b/flight/flight-sql-jdbc-core/pom.xml
index da00baf32a..be2ee32868 100644
--- a/flight/flight-sql-jdbc-core/pom.xml
+++ b/flight/flight-sql-jdbc-core/pom.xml
@@ -22,7 +22,7 @@ under the License.
org.apache.arrow
arrow-flight
- 19.0.0-SNAPSHOT
+ 20.0.0-SNAPSHOT
flight-sql-jdbc-core
@@ -105,7 +105,7 @@ under the License.
commons-io
commons-io
- 2.21.0
+ 2.22.0
test
@@ -123,25 +123,25 @@ under the License.
com.squareup.okhttp3
mockwebserver3
- 5.3.2
+ 5.4.0
test
com.squareup.okhttp3
mockwebserver3-junit5
- 5.3.2
+ 5.4.0
test
com.squareup.okhttp3
okhttp-jvm
- 5.3.2
+ 5.4.0
test
com.squareup.okio
okio-jvm
- 3.16.4
+ 3.17.0
test
@@ -159,13 +159,13 @@ under the License.
org.apache.calcite.avatica
avatica
- 1.26.0
+ 1.27.0
org.bouncycastle
bcpkix-jdk18on
- 1.83
+ 1.84
@@ -176,13 +176,13 @@ under the License.
com.github.ben-manes.caffeine
caffeine
- 3.2.3
+ 3.2.4
com.nimbusds
oauth2-oidc-sdk
- 11.20.1
+ 11.37.2
diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowDatabaseMetadata.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowDatabaseMetadata.java
index 502270e1cd..0110525fea 100644
--- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowDatabaseMetadata.java
+++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowDatabaseMetadata.java
@@ -45,6 +45,7 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
@@ -86,9 +87,12 @@
import org.apache.arrow.vector.util.Text;
import org.apache.calcite.avatica.AvaticaConnection;
import org.apache.calcite.avatica.AvaticaDatabaseMetaData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/** Arrow Flight JDBC's implementation of {@link DatabaseMetaData}. */
public class ArrowDatabaseMetadata extends AvaticaDatabaseMetaData {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ArrowDatabaseMetadata.class);
private static final String JAVA_REGEX_SPECIALS = "[]()|^-+*?{}$\\.";
private static final Charset CHARSET = StandardCharsets.UTF_8;
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
@@ -774,7 +778,34 @@ private T getSqlInfoAndCacheIfCacheIsEmpty(
}
}
}
- return desiredType.cast(cachedSqlInfo.get(sqlInfoCommand));
+ T value = desiredType.cast(cachedSqlInfo.get(sqlInfoCommand));
+ if (value != null) {
+ return value;
+ }
+ LOGGER.debug(
+ "SqlInfo {} not provided by server, returning default for type {}",
+ sqlInfoCommand.name(),
+ desiredType.getSimpleName());
+
+ // Return sensible defaults when SqlInfo is unavailable
+ if (desiredType == Long.class) {
+ return desiredType.cast(0L);
+ } else if (desiredType == Integer.class) {
+ return desiredType.cast(0);
+ } else if (desiredType == Boolean.class) {
+ return desiredType.cast(false);
+ } else if (desiredType == String.class) {
+ return desiredType.cast("");
+ } else if (desiredType == Map.class) {
+ return desiredType.cast(Collections.emptyMap());
+ } else if (desiredType == List.class) {
+ return desiredType.cast(Collections.emptyList());
+ }
+
+ throw new SQLException(
+ String.format(
+ "The value of the SqlInfo %s is null and it could not be cast to %s.",
+ sqlInfoCommand.name(), desiredType.getName()));
}
private Optional convertListSqlInfoToString(final List> sqlInfoList) {
diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowFlightJdbcDriver.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowFlightJdbcDriver.java
index 53e6120f62..12ef8030d7 100644
--- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowFlightJdbcDriver.java
+++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowFlightJdbcDriver.java
@@ -75,7 +75,9 @@ public Logger getParentLogger() {
public ArrowFlightConnection connect(final String url, final Properties info)
throws SQLException {
final Properties properties = new Properties(info);
- properties.putAll(info);
+ if (info != null) {
+ properties.putAll(info);
+ }
if (url != null) {
final Optional
+
diff --git a/vector/src/main/codegen/templates/HolderReaderImpl.java b/vector/src/main/codegen/templates/HolderReaderImpl.java
index 1151ea5d39..cdbb65c4f6 100644
--- a/vector/src/main/codegen/templates/HolderReaderImpl.java
+++ b/vector/src/main/codegen/templates/HolderReaderImpl.java
@@ -126,7 +126,7 @@ public void read(Nullable${name}Holder h) {
<#elseif minor.class == "Duration">
return DurationVector.toDuration(holder.value, holder.unit);
<#elseif minor.class == "Bit" >
- return new Boolean(holder.value != 0);
+ return Boolean.valueOf(holder.value != 0);
<#elseif minor.class == "Decimal">
byte[] bytes = new byte[${type.width}];
holder.buffer.getBytes(holder.start, bytes, 0, ${type.width});
@@ -151,7 +151,7 @@ public void read(Nullable${name}Holder h) {
<#elseif minor.class == "TimeStampNano">
return DateUtility.getLocalDateTimeFromEpochNano(holder.value);
<#else>
- ${friendlyType} value = new ${friendlyType}(this.holder.value);
+ ${friendlyType} value = ${friendlyType}.valueOf(this.holder.value);
return value;
#if>
}
diff --git a/vector/src/main/codegen/templates/UnionFixedSizeListWriter.java b/vector/src/main/codegen/templates/UnionFixedSizeListWriter.java
index f6e3f63caf..484199ab2a 100644
--- a/vector/src/main/codegen/templates/UnionFixedSizeListWriter.java
+++ b/vector/src/main/codegen/templates/UnionFixedSizeListWriter.java
@@ -35,6 +35,10 @@
<#include "/@includes/vv_imports.ftl" />
+<#function is_timestamp_tz type>
+ <#return type?starts_with("TimeStamp") && type?ends_with("TZ")>
+#function>
+
/*
* This class is generated using freemarker and the ${.template_name} template.
*/
@@ -96,55 +100,30 @@ public void close() throws Exception {
public void setPosition(int index) {
super.setPosition(index);
}
- <#list vv.types as type><#list type.minor as minor><#assign name = minor.class?cap_first />
- <#assign fields = minor.fields!type.fields />
- <#assign uncappedName = name?uncap_first/>
- <#if uncappedName == "int" ><#assign uncappedName = "integer" />#if>
- <#if !minor.typeParams?? >
+ <#list vv.types as type><#list type.minor as minor>
+ <#assign lowerName = minor.class?uncap_first />
+ <#if lowerName == "int" ><#assign lowerName = "integer" />#if>
+ <#assign upperName = minor.class?upper_case />
@Override
- public ${name}Writer ${uncappedName}() {
+ public ${minor.class}Writer ${lowerName}() {
return this;
}
+ <#if minor.typeParams?? >
@Override
- public ${name}Writer ${uncappedName}(String name) {
- structName = name;
- return writer.${uncappedName}(name);
+ public ${minor.class}Writer ${lowerName}(String name<#list minor.typeParams as typeParam>, ${typeParam.type} ${typeParam.name}#list>) {
+ return writer.${lowerName}(name<#list minor.typeParams as typeParam>, ${typeParam.name}#list>);
}
#if>
- #list>#list>
-
- @Override
- public DecimalWriter decimal() {
- return this;
- }
-
- @Override
- public DecimalWriter decimal(String name, int scale, int precision) {
- return writer.decimal(name, scale, precision);
- }
-
- @Override
- public DecimalWriter decimal(String name) {
- return writer.decimal(name);
- }
-
@Override
- public Decimal256Writer decimal256() {
- return this;
- }
-
- @Override
- public Decimal256Writer decimal256(String name, int scale, int precision) {
- return writer.decimal256(name, scale, precision);
+ public ${minor.class}Writer ${lowerName}(String name) {
+ structName = name;
+ return writer.${lowerName}(name);
}
- @Override
- public Decimal256Writer decimal256(String name) {
- return writer.decimal256(name);
- }
+ #list>#list>
@Override
public StructWriter struct() {
@@ -215,87 +194,86 @@ public void end() {
}
@Override
- public void write(DecimalHolder holder) {
- if (writer.idx() >= (idx() + 1) * listSize) {
- throw new IllegalStateException(String.format("values at index %s is greater than listSize %s", idx(), listSize));
- }
- writer.write(holder);
- writer.setPosition(writer.idx() + 1);
- }
-
- @Override
- public void write(Decimal256Holder holder) {
+ public void writeNull() {
if (writer.idx() >= (idx() + 1) * listSize) {
throw new IllegalStateException(String.format("values at index %s is greater than listSize %s", idx(), listSize));
}
- writer.write(holder);
- writer.setPosition(writer.idx() + 1);
+ writer.writeNull();
}
+ <#list vv.types as type>
+ <#list type.minor as minor>
+ <#assign name = minor.class?cap_first />
+ <#assign fields = minor.fields!type.fields />
+ <#assign uncappedName = name?uncap_first/>
@Override
- public void writeNull() {
+ public void write${name}(<#list fields as field>${field.type} ${field.name}<#if field_has_next>, #if>#list>) {
if (writer.idx() >= (idx() + 1) * listSize) {
throw new IllegalStateException(String.format("values at index %s is greater than listSize %s", idx(), listSize));
}
- writer.writeNull();
+ writer.write${name}(<#list fields as field>${field.name}<#if field_has_next>, #if>#list>);
+ writer.setPosition(writer.idx()+1);
}
- public void writeDecimal(long start, ArrowBuf buffer, ArrowType arrowType) {
+ <#if is_timestamp_tz(minor.class) || minor.class == "Duration" || minor.class == "FixedSizeBinary">
+ @Override
+ public void write(${name}Holder holder) {
if (writer.idx() >= (idx() + 1) * listSize) {
throw new IllegalStateException(String.format("values at index %s is greater than listSize %s", idx(), listSize));
}
- writer.writeDecimal(start, buffer, arrowType);
- writer.setPosition(writer.idx() + 1);
+ writer.write(holder);
+ writer.setPosition(writer.idx()+1);
}
- public void writeDecimal(BigDecimal value) {
+ <#elseif minor.class?starts_with("Decimal")>
+ @Override
+ public void write${name}(long start, ArrowBuf buffer, ArrowType arrowType) {
if (writer.idx() >= (idx() + 1) * listSize) {
throw new IllegalStateException(String.format("values at index %s is greater than listSize %s", idx(), listSize));
}
- writer.writeDecimal(value);
- writer.setPosition(writer.idx() + 1);
+ writer.write${name}(start, buffer, arrowType);
+ writer.setPosition(writer.idx()+1);
}
- public void writeBigEndianBytesToDecimal(byte[] value, ArrowType arrowType) {
+ @Override
+ public void write(${name}Holder holder) {
if (writer.idx() >= (idx() + 1) * listSize) {
throw new IllegalStateException(String.format("values at index %s is greater than listSize %s", idx(), listSize));
}
- writer.writeBigEndianBytesToDecimal(value, arrowType);
- writer.setPosition(writer.idx() + 1);
+ writer.write(holder);
+ writer.setPosition(writer.idx()+1);
}
- public void writeDecimal256(long start, ArrowBuf buffer, ArrowType arrowType) {
+ @Override
+ public void write${name}(BigDecimal value) {
if (writer.idx() >= (idx() + 1) * listSize) {
throw new IllegalStateException(String.format("values at index %s is greater than listSize %s", idx(), listSize));
}
- writer.writeDecimal256(start, buffer, arrowType);
- writer.setPosition(writer.idx() + 1);
+ writer.write${name}(value);
+ writer.setPosition(writer.idx()+1);
}
- public void writeDecimal256(BigDecimal value) {
+ @Override
+ public void writeBigEndianBytesTo${name}(byte[] value, ArrowType arrowType){
if (writer.idx() >= (idx() + 1) * listSize) {
throw new IllegalStateException(String.format("values at index %s is greater than listSize %s", idx(), listSize));
}
- writer.writeDecimal256(value);
+ writer.writeBigEndianBytesTo${name}(value, arrowType);
writer.setPosition(writer.idx() + 1);
}
-
- public void writeBigEndianBytesToDecimal256(byte[] value, ArrowType arrowType) {
+ <#else>
+ @Override
+ public void write(${name}Holder holder) {
if (writer.idx() >= (idx() + 1) * listSize) {
throw new IllegalStateException(String.format("values at index %s is greater than listSize %s", idx(), listSize));
}
- writer.writeBigEndianBytesToDecimal256(value, arrowType);
- writer.setPosition(writer.idx() + 1);
+ writer.write${name}(<#list fields as field>holder.${field.name}<#if field_has_next>, #if>#list>);
+ writer.setPosition(writer.idx()+1);
}
+ #if>
-
- <#list vv.types as type>
- <#list type.minor as minor>
- <#assign name = minor.class?cap_first />
- <#assign fields = minor.fields!type.fields />
- <#assign uncappedName = name?uncap_first/>
- <#if minor.class?ends_with("VarBinary")>
+ <#if minor.class?ends_with("VarBinary")>
@Override
public void write${minor.class}(byte[] value) {
if (writer.idx() >= (idx() + 1) * listSize) {
@@ -349,27 +327,8 @@ public void writeBigEndianBytesToDecimal256(byte[] value, ArrowType arrowType) {
writer.write${minor.class}(value);
writer.setPosition(writer.idx() + 1);
}
- #if>
-
- <#if !minor.typeParams?? >
- @Override
- public void write${name}(<#list fields as field>${field.type} ${field.name}<#if field_has_next>, #if>#list>) {
- if (writer.idx() >= (idx() + 1) * listSize) {
- throw new IllegalStateException(String.format("values at index %s is greater than listSize %s", idx(), listSize));
- }
- writer.write${name}(<#list fields as field>${field.name}<#if field_has_next>, #if>#list>);
- writer.setPosition(writer.idx() + 1);
- }
-
- public void write(${name}Holder holder) {
- if (writer.idx() >= (idx() + 1) * listSize) {
- throw new IllegalStateException(String.format("values at index %s is greater than listSize %s", idx(), listSize));
- }
- writer.write${name}(<#list fields as field>holder.${field.name}<#if field_has_next>, #if>#list>);
- writer.setPosition(writer.idx() + 1);
- }
+ #if>
- #if>
#list>
#list>
}
diff --git a/vector/src/main/codegen/templates/UnionListWriter.java b/vector/src/main/codegen/templates/UnionListWriter.java
index 4b54739230..394348f029 100644
--- a/vector/src/main/codegen/templates/UnionListWriter.java
+++ b/vector/src/main/codegen/templates/UnionListWriter.java
@@ -123,8 +123,6 @@ public void setPosition(int index) {
<#assign lowerName = minor.class?uncap_first />
<#if lowerName == "int" ><#assign lowerName = "integer" />#if>
<#assign upperName = minor.class?upper_case />
- <#assign capName = minor.class?cap_first />
- <#assign vectName = capName />
@Override
public ${minor.class}Writer ${lowerName}() {
return this;
@@ -370,6 +368,7 @@ public void write(${name}Holder holder) {
}
<#elseif minor.class?starts_with("Decimal")>
+ @Override
public void write${name}(long start, ArrowBuf buffer, ArrowType arrowType) {
writer.write${name}(start, buffer, arrowType);
writer.setPosition(writer.idx()+1);
@@ -381,11 +380,13 @@ public void write(${name}Holder holder) {
writer.setPosition(writer.idx()+1);
}
+ @Override
public void write${name}(BigDecimal value) {
writer.write${name}(value);
writer.setPosition(writer.idx()+1);
}
+ @Override
public void writeBigEndianBytesTo${name}(byte[] value, ArrowType arrowType){
writer.writeBigEndianBytesTo${name}(value, arrowType);
writer.setPosition(writer.idx() + 1);
@@ -429,6 +430,7 @@ public void write(${name}Holder holder) {
writer.setPosition(writer.idx() + 1);
}
+ @Override
public void write${minor.class}(String value) {
writer.write${minor.class}(value);
writer.setPosition(writer.idx() + 1);
diff --git a/vector/src/main/java/org/apache/arrow/vector/BaseLargeVariableWidthVector.java b/vector/src/main/java/org/apache/arrow/vector/BaseLargeVariableWidthVector.java
index 6c451f10a7..3fac195786 100644
--- a/vector/src/main/java/org/apache/arrow/vector/BaseLargeVariableWidthVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/BaseLargeVariableWidthVector.java
@@ -373,14 +373,26 @@ private void setReaderAndWriterIndex() {
valueBuffer.readerIndex(0);
if (valueCount == 0) {
validityBuffer.writerIndex(0);
- offsetBuffer.writerIndex(0);
valueBuffer.writerIndex(0);
} else {
final long lastDataOffset = getStartOffset(valueCount);
validityBuffer.writerIndex(BitVectorHelper.getValidityBufferSizeFromCount(valueCount));
- offsetBuffer.writerIndex((long) (valueCount + 1) * OFFSET_WIDTH);
valueBuffer.writerIndex(lastDataOffset);
}
+ // IPC serializer will determine readable bytes based on `readerIndex` and `writerIndex`.
+ // Both are set to 0 means 0 bytes are written to the IPC stream which will crash IPC readers
+ // in other libraries. According to Arrow spec, we should still output the offset buffer which
+ // is [0].
+ final long requiredOffsetBufferSize = (long) (valueCount + 1) * OFFSET_WIDTH;
+ if (offsetBuffer.capacity() < requiredOffsetBufferSize) {
+ ArrowBuf newOffsetBuffer = allocateOffsetBuffer(requiredOffsetBufferSize);
+ if (offsetBuffer.capacity() > 0) {
+ newOffsetBuffer.setBytes(0, offsetBuffer, 0, offsetBuffer.capacity());
+ }
+ offsetBuffer.getReferenceManager().release();
+ offsetBuffer = newOffsetBuffer;
+ }
+ offsetBuffer.writerIndex(requiredOffsetBufferSize);
}
/** Same as {@link #allocateNewSafe()}. */
diff --git a/vector/src/main/java/org/apache/arrow/vector/BaseVariableWidthVector.java b/vector/src/main/java/org/apache/arrow/vector/BaseVariableWidthVector.java
index 96e2afbd29..d5bd167256 100644
--- a/vector/src/main/java/org/apache/arrow/vector/BaseVariableWidthVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/BaseVariableWidthVector.java
@@ -389,14 +389,26 @@ private void setReaderAndWriterIndex() {
valueBuffer.readerIndex(0);
if (valueCount == 0) {
validityBuffer.writerIndex(0);
- offsetBuffer.writerIndex(0);
valueBuffer.writerIndex(0);
} else {
final int lastDataOffset = getStartOffset(valueCount);
validityBuffer.writerIndex(BitVectorHelper.getValidityBufferSizeFromCount(valueCount));
- offsetBuffer.writerIndex((long) (valueCount + 1) * OFFSET_WIDTH);
valueBuffer.writerIndex(lastDataOffset);
}
+ // IPC serializer will determine readable bytes based on `readerIndex` and `writerIndex`.
+ // Both are set to 0 means 0 bytes are written to the IPC stream which will crash IPC readers
+ // in other libraries. According to Arrow spec, we should still output the offset buffer which
+ // is [0].
+ final long requiredOffsetBufferSize = (long) (valueCount + 1) * OFFSET_WIDTH;
+ if (offsetBuffer.capacity() < requiredOffsetBufferSize) {
+ ArrowBuf newOffsetBuffer = allocateOffsetBuffer(requiredOffsetBufferSize);
+ if (offsetBuffer.capacity() > 0) {
+ newOffsetBuffer.setBytes(0, offsetBuffer, 0, offsetBuffer.capacity());
+ }
+ offsetBuffer.getReferenceManager().release();
+ offsetBuffer = newOffsetBuffer;
+ }
+ offsetBuffer.writerIndex(requiredOffsetBufferSize);
}
/** Same as {@link #allocateNewSafe()}. */
diff --git a/vector/src/main/java/org/apache/arrow/vector/VectorSchemaRoot.java b/vector/src/main/java/org/apache/arrow/vector/VectorSchemaRoot.java
index a7cb9ced72..4c1fbf761a 100644
--- a/vector/src/main/java/org/apache/arrow/vector/VectorSchemaRoot.java
+++ b/vector/src/main/java/org/apache/arrow/vector/VectorSchemaRoot.java
@@ -199,13 +199,18 @@ public FieldVector getVector(int index) {
*/
public VectorSchemaRoot addVector(int index, FieldVector vector) {
Preconditions.checkNotNull(vector);
- Preconditions.checkArgument(index >= 0 && index < fieldVectors.size());
+ Preconditions.checkArgument(index >= 0 && index <= fieldVectors.size());
List