diff --git a/.env b/.env
index d3e1c1d63a..ef0c5fb101 100644
--- a/.env
+++ b/.env
@@ -40,18 +40,17 @@ ARCH_SHORT=amd64
# Default repository to pull and push images from
REPO=ghcr.io/apache/arrow-java-dev
-ARROW_REPO=apache/arrow-dev
+ARROW_REPO=ghcr.io/apache/arrow-dev
# The setup attempts to generate coredumps by default, in order to disable the
# coredump generation set it to 0
ULIMIT_CORE=-1
# Default versions for various dependencies
-JDK=11
+JDK=17
MAVEN=3.9.9
# Versions for various dependencies used to build artifacts
# Keep in sync with apache/arrow
ARROW_REPO_ROOT=./arrow
-PYTHON=3.9
-VCPKG="f7423ee180c4b7f40d43402c2feb3859161ef625" # 2024.06.15 Release
+VCPKG="9b965a116838c6cdcd36bca60d1b81b030c8ab8d" # 2026.05.27 (not release, upstream commit)
diff --git a/.github/workflows/comment_bot.yml b/.github/workflows/comment_bot.yml
index 5fbc858cc6..507d6a969c 100644
--- a/.github/workflows/comment_bot.yml
+++ b/.github/workflows/comment_bot.yml
@@ -30,7 +30,7 @@ jobs:
if: github.event.comment.body == 'take'
runs-on: ubuntu-latest
steps:
- - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
+ - uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |-
diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml
index 37320898a6..f4dc9ad6f1 100644
--- a/.github/workflows/dev.yml
+++ b/.github/workflows/dev.yml
@@ -19,7 +19,9 @@ name: Dev
on:
pull_request: {}
- push: {}
+ push:
+ branches-ignore:
+ - dependabot/**
concurrency:
group: ${{ github.repository }}-${{ github.ref }}-${{ github.workflow }}
@@ -33,16 +35,16 @@ jobs:
name: "pre-commit"
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- - uses: actions/setup-python@v5
+ - uses: actions/setup-python@v7
with:
python-version: '3.x'
- name: pre-commit (cache)
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
diff --git a/.github/workflows/dev_pr.yml b/.github/workflows/dev_pr.yml
index 34b3363c50..ad000df88e 100644
--- a/.github/workflows/dev_pr.yml
+++ b/.github/workflows/dev_pr.yml
@@ -35,6 +35,7 @@ concurrency:
permissions:
contents: read
+ issues: write
pull-requests: write
jobs:
@@ -42,35 +43,35 @@ jobs:
name: "Ensure PR format"
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- name: Ensure PR title format
id: title-format
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
+ uses: actions/github-script@v9
with:
script: |
const scripts = require(`${process.env.GITHUB_WORKSPACE}/.github/workflows/dev_pr.js`);
return scripts.check_title_format({core, github, context});
- name: Label PR
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
+ uses: actions/github-script@v9
with:
script: |
const scripts = require(`${process.env.GITHUB_WORKSPACE}/.github/workflows/dev_pr.js`);
await scripts.apply_labels({core, github, context});
- name: Ensure PR is labeled
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
+ uses: actions/github-script@v9
with:
script: |
const scripts = require(`${process.env.GITHUB_WORKSPACE}/.github/workflows/dev_pr.js`);
await scripts.check_labels({core, github, context});
- name: Ensure PR is linked to an issue
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
+ uses: actions/github-script@v9
with:
script: |
const scripts = require(`${process.env.GITHUB_WORKSPACE}/.github/workflows/dev_pr.js`);
diff --git a/.github/workflows/dev_pr_milestone.sh b/.github/workflows/dev_pr_milestone.sh
index b6876b4b08..4a77eb1f73 100755
--- a/.github/workflows/dev_pr_milestone.sh
+++ b/.github/workflows/dev_pr_milestone.sh
@@ -37,8 +37,8 @@ main() {
local -r milestone=$(
gh api "/repos/${repo}/milestones" |
jq --raw-output '.[] | .title' |
- grep -E '^[0-9]+\.[0-9]+\.[0-9]+$'
- head -n1
+ grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' |
+ head -n1
)
echo "Assigning milestone: ${milestone}"
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
new file mode 100644
index 0000000000..3872d03d2f
--- /dev/null
+++ b/.github/workflows/integration.yml
@@ -0,0 +1,127 @@
+# 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.
+
+name: Integration
+
+on:
+ push:
+ branches:
+ - '**'
+ - '!dependabot/**'
+ tags:
+ - '**'
+ paths:
+ - '.github/workflows/integration.yml'
+ - '**/pom.xml'
+ - 'c/**'
+ - 'ci/scripts/**'
+ - 'compose.yaml'
+ - 'flight/**'
+ - 'format/**'
+ - 'testing/data/**'
+ - 'vector/**'
+ pull_request:
+ paths:
+ - '.github/workflows/integration.yml'
+ - '**/pom.xml'
+ - 'c/**'
+ - 'ci/scripts/**'
+ - 'compose.yaml'
+ - 'flight/**'
+ - 'format/**'
+ - 'testing/data/**'
+ - 'vector/**'
+
+concurrency:
+ group: ${{ github.repository }}-${{ github.head_ref || github.sha }}-${{ github.workflow }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+env:
+ DOCKER_VOLUME_PREFIX: ".docker/"
+
+jobs:
+ integration:
+ name: AMD64 integration
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ steps:
+ - name: Checkout Arrow
+ uses: actions/checkout@v7
+ with:
+ fetch-depth: 0
+ repository: apache/arrow
+ submodules: recursive
+ - name: Checkout Arrow Rust
+ uses: actions/checkout@v7
+ with:
+ repository: apache/arrow-rs
+ path: rust
+ - name: Checkout Arrow nanoarrow
+ uses: actions/checkout@v7
+ with:
+ repository: apache/arrow-nanoarrow
+ path: nanoarrow
+ - name: Checkout Arrow .NET
+ uses: actions/checkout@v7
+ with:
+ repository: apache/arrow-dotnet
+ path: dotnet
+ - name: Checkout Arrow Go
+ uses: actions/checkout@v7
+ with:
+ repository: apache/arrow-go
+ path: go
+ - name: Checkout Arrow Java
+ uses: actions/checkout@v7
+ with:
+ path: java
+ - name: Checkout Arrow JavaScript
+ uses: actions/checkout@v7
+ with:
+ repository: apache/arrow-js
+ path: js
+ - name: Free up disk space
+ run: |
+ ci/scripts/util_free_space.sh
+ - name: Cache Docker Volumes
+ uses: actions/cache@v6
+ with:
+ path: .docker
+ key: integration-conda-${{ hashFiles('cpp/**') }}
+ restore-keys: integration-conda-
+ - name: Setup Python
+ uses: actions/setup-python@v6
+ with:
+ python-version: 3.12
+ - name: Setup Archery
+ run: pip install -e dev/archery[docker]
+ - name: Execute Docker Build
+ run: |
+ source ci/scripts/util_enable_core_dumps.sh
+ archery docker run \
+ -e ARCHERY_DEFAULT_BRANCH=main \
+ -e ARCHERY_INTEGRATION_TARGET_IMPLEMENTATIONS=java \
+ -e ARCHERY_INTEGRATION_WITH_DOTNET=1 \
+ -e ARCHERY_INTEGRATION_WITH_GO=1 \
+ -e ARCHERY_INTEGRATION_WITH_JAVA=1 \
+ -e ARCHERY_INTEGRATION_WITH_JS=1 \
+ -e ARCHERY_INTEGRATION_WITH_NANOARROW=1 \
+ -e ARCHERY_INTEGRATION_WITH_RUST=1 \
+ conda-integration
diff --git a/.github/workflows/rc.yml b/.github/workflows/rc.yml
index 7e3cf5f6f2..18a721ac02 100644
--- a/.github/workflows/rc.yml
+++ b/.github/workflows/rc.yml
@@ -38,7 +38,7 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@v7
with:
submodules: recursive
- name: Prepare for tag
@@ -71,7 +71,7 @@ jobs:
run: |
dev/release/run_rat.sh "${TAR_GZ}"
- name: Upload source archive
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-source
path: |
@@ -101,45 +101,39 @@ jobs:
packages: write
steps:
- name: Download source archive
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-source
- name: Extract source archive
run: |
tar -xf apache-arrow-java-*.tar.gz --strip-components=1
- # We always use the main branch for apache/arrow for now.
- # Because we want to use
- # https://github.com/apache/arrow/pull/45114 in
- # apache/arrow-java. We can revert this workaround once Apache
- # Arrow 20.0.0 that includes the change released.
- #
- # - name: Download the latest Apache Arrow C++
- # if: github.event_name != 'schedule'
- # run: |
- # ci/scripts/download_cpp.sh
+ - name: Download the latest Apache Arrow C++
+ if: github.event_name != 'schedule'
+ run: |
+ ci/scripts/download_cpp.sh
- name: Checkout Apache Arrow C++
- # if: github.event_name == 'schedule'
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ if: github.event_name == 'schedule'
+ uses: actions/checkout@v7
with:
repository: apache/arrow
path: arrow
- name: Checkout apache/arrow-testing
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@v7
with:
repository: apache/arrow-testing
path: arrow/testing
- name: Checkout apache/parquet-testing
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@v7
with:
repository: apache/parquet-testing
path: arrow/cpp/submodules/parquet-testing
- - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
+ - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Cache
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .docker
key: jni-linux-${{ matrix.platform.arch }}-${{ hashFiles('arrow/cpp/**') }}
@@ -154,54 +148,54 @@ jobs:
- name: Compress into single artifact to keep directory structure
run: tar -cvzf jni-linux-${{ matrix.platform.arch }}.tar.gz jni/
- name: Upload artifacts
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: jni-linux-${{ matrix.platform.arch }}
path: jni-linux-${{ matrix.platform.arch }}.tar.gz
jni-macos:
name: JNI ${{ matrix.platform.runs_on }} ${{ matrix.platform.arch }}
runs-on: ${{ matrix.platform.runs_on }}
- timeout-minutes: 45
+ timeout-minutes: 60
needs:
- source
strategy:
fail-fast: false
matrix:
platform:
- - { runs_on: macos-13, arch: "x86_64"}
+ - { runs_on: macos-15-intel, arch: "x86_64"}
- { runs_on: macos-14, arch: "aarch_64" }
env:
MACOSX_DEPLOYMENT_TARGET: "14.0"
steps:
- name: Download source archive
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-source
- name: Extract source archive
run: |
tar -xf apache-arrow-java-*.tar.gz --strip-components=1
- # - name: Download the latest Apache Arrow C++
- # if: github.event_name != 'schedule'
- # run: |
- # ci/scripts/download_cpp.sh
+ - name: Download the latest Apache Arrow C++
+ if: github.event_name != 'schedule'
+ run: |
+ ci/scripts/download_cpp.sh
- name: Checkout Apache Arrow C++
- # if: github.event_name == 'schedule'
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ if: github.event_name == 'schedule'
+ uses: actions/checkout@v7
with:
repository: apache/arrow
path: arrow
- name: Checkout apache/arrow-testing
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@v7
with:
repository: apache/arrow-testing
path: arrow/testing
- name: Checkout apache/parquet-testing
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@v7
with:
repository: apache/parquet-testing
path: arrow/cpp/submodules/parquet-testing
- name: Set up Python
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
+ uses: actions/setup-python@v7
with:
cache: 'pip'
python-version: 3.12
@@ -221,6 +215,10 @@ jobs:
# llvm@14 because llvm is newer than llvm@14.
brew uninstall llvm || :
+ # We can remove this when we drop support for
+ # macos-15-intel. because macos-14 or later with arm64 uses /opt/homebrew/
+ # not /usr/local/.
+ #
# Ensure updating python@XXX with the "--overwrite" option.
# If python@XXX is updated without "--overwrite", it causes
# a conflict error. Because Python 3 installed not by
@@ -229,10 +227,10 @@ jobs:
# tries to replace /usr/local/bin/2to3 and so on and causes
# a conflict error.
brew update
- for python_package in $(brew list | grep python@); do
+ for python_package in $(brew list | grep python@ | sort -r); do
brew install --overwrite ${python_package}
done
- brew install --overwrite python
+ brew install --overwrite python3
if [ "$(uname -m)" = "arm64" ]; then
# pkg-config formula is deprecated but it's still installed
@@ -266,7 +264,7 @@ jobs:
run: |
echo "CCACHE_DIR=${PWD}/ccache" >> ${GITHUB_ENV}
- name: Cache ccache
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ccache
key: jni-macos-${{ matrix.platform.arch }}-${{ hashFiles('arrow/cpp/**') }}
@@ -275,12 +273,12 @@ jobs:
run: |
set -e
# make brew Java available to CMake
- export JAVA_HOME=$(brew --prefix openjdk@11)/libexec/openjdk.jdk/Contents/Home
+ export JAVA_HOME=$(brew --prefix openjdk@17)/libexec/openjdk.jdk/Contents/Home
ci/scripts/jni_macos_build.sh . arrow build jni
- name: Compress into single artifact to keep directory structure
run: tar -cvzf jni-macos-${{ matrix.platform.arch }}.tar.gz jni/
- name: Upload artifacts
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: jni-macos-${{ matrix.platform.arch }}
path: jni-macos-${{ matrix.platform.arch }}.tar.gz
@@ -294,11 +292,11 @@ jobs:
fail-fast: false
matrix:
platform:
- - runs_on: windows-2019
+ - runs_on: windows-2022
arch: "x86_64"
steps:
- name: Download source archive
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-source
- name: Extract source archive
@@ -312,14 +310,14 @@ jobs:
ci/scripts/download_cpp.sh
- name: Checkout Apache Arrow C++
if: github.event_name == 'schedule'
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@v7
with:
repository: apache/arrow
path: arrow
- name: Set up Java
- uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
+ uses: actions/setup-java@v5
with:
- java-version: '11'
+ java-version: '17'
distribution: 'temurin'
- name: Download Timezone Database
shell: bash
@@ -342,7 +340,7 @@ jobs:
run: |
echo "CCACHE_DIR=${PWD}/ccache" >> ${GITHUB_ENV}
- name: Cache ccache
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ccache
key: jni-windows-${{ matrix.platform.arch }}-${{ hashFiles('arrow/cpp/**') }}
@@ -350,7 +348,7 @@ jobs:
- name: Build
shell: cmd
run: |
- call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
+ call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
REM For ORC
set TZDIR=/c/msys64/usr/share/zoneinfo
bash -c "ci/scripts/jni_windows_build.sh . arrow build jni"
@@ -358,7 +356,7 @@ jobs:
shell: bash
run: tar -cvzf jni-windows-${{ matrix.platform.arch }}.tar.gz jni/
- name: Upload artifacts
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: jni-windows-${{ matrix.platform.arch }}
path: jni-windows-${{ matrix.platform.arch }}.tar.gz
@@ -371,7 +369,7 @@ jobs:
- jni-windows
steps:
- name: Download artifacts
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: artifacts
- name: Decompress artifacts
@@ -411,12 +409,12 @@ jobs:
test -f jni/arrow_dataset_jni/x86_64/arrow_dataset_jni.dll
test -f jni/arrow_orc_jni/x86_64/arrow_orc_jni.dll
- name: Checkout apache/arrow-testing
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@v7
with:
repository: apache/arrow-testing
path: testing
- name: Cache ~/.m2
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.m2
key: binaries-build-${{ hashFiles('**/*.java', '**/pom.xml') }}
@@ -430,12 +428,12 @@ jobs:
cp -a target/site/apidocs reference
tar -cvzf reference.tar.gz reference
- name: Upload binaries
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-binaries
path: binaries/*
- name: Upload docs
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: reference
path: reference.tar.gz
@@ -448,15 +446,15 @@ jobs:
contents: read
packages: write
steps:
- - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
+ - uses: actions/setup-python@v7
with:
cache: 'pip'
- name: Download source archive
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-source
- name: Download Javadocs
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: reference
- name: Extract source archive
@@ -473,7 +471,7 @@ jobs:
- name: Compress into single artifact to keep directory structure
run: tar -cvzf html.tar.gz -C docs/build html
- name: Upload artifacts
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-html
path: html.tar.gz
@@ -499,7 +497,7 @@ jobs:
contents: write
steps:
- name: Checkout
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@v7
with:
path: site
- name: Prepare branch
@@ -521,7 +519,7 @@ jobs:
cp ../.asf.yaml ./
git add .nojekyll .asf.yaml
- name: Download
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-html
- name: Extract
@@ -557,7 +555,7 @@ jobs:
- ubuntu-latest
steps:
- name: Download release artifacts
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: release-*
- name: Verify
@@ -591,7 +589,7 @@ jobs:
contents: write
steps:
- name: Download release artifacts
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: release-*
path: artifacts
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a955964cd8..7692eb6cbe 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -32,7 +32,7 @@ jobs:
publish:
name: Publish
runs-on: ubuntu-latest
- timeout-minutes: 5
+ timeout-minutes: 30
steps:
- name: Download RC contents
run: |
@@ -65,7 +65,7 @@ jobs:
$artifact
done
- name: Checkout for publishing docs
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@v7
with:
path: site
- name: Publish docs
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 5db5c988eb..653b16fa32 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -45,7 +45,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- jdk: [11, 17, 21, 23]
+ jdk: [17, 21, 23]
maven: [3.9.9]
image: [ubuntu, conda-jni-cdata]
include:
@@ -58,15 +58,15 @@ jobs:
MAVEN: ${{ matrix.maven }}
steps:
- name: Checkout Arrow
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
- name: Cache Docker Volumes
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ uses: actions/cache@v6
with:
path: .docker
- key: maven-${{ matrix.jdk }}-${{ matrix.maven }}-${{ hashFiles('compose.yaml', '**/pom.xml', '**/*.java') }}
+ key: maven-${{ matrix.jdk }}-${{ matrix.maven }}-${{ hashFiles('compose.yaml', '**/pom.xml') }}
restore-keys: maven-${{ matrix.jdk }}-${{ matrix.maven }}-
- name: Execute Docker Build
env:
@@ -88,22 +88,23 @@ jobs:
matrix:
include:
- arch: AMD64
- jdk: 11
- macos: 13
+ jdk: 17
+ macos: 15-intel
- arch: AArch64
- jdk: 11
+ jdk: 17
macos: latest
steps:
- - name: Set up Java
- uses: actions/setup-java@v4
- with:
- distribution: 'temurin'
- java-version: ${{ matrix.jdk }}
- name: Checkout Arrow
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
+ - name: Set up Java
+ uses: actions/setup-java@v5
+ with:
+ distribution: 'temurin'
+ java-version: ${{ matrix.jdk }}
+ cache: 'maven'
- name: Build
shell: bash
env:
@@ -123,18 +124,19 @@ jobs:
strategy:
fail-fast: false
matrix:
- jdk: [11]
+ jdk: [17]
steps:
- - name: Set up Java
- uses: actions/setup-java@v4
- with:
- java-version: ${{ matrix.jdk }}
- distribution: 'temurin'
- name: Checkout Arrow
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
+ - name: Set up Java
+ uses: actions/setup-java@v5
+ with:
+ java-version: ${{ matrix.jdk }}
+ distribution: 'temurin'
+ cache: 'maven'
- name: Build
shell: bash
env:
@@ -145,60 +147,3 @@ jobs:
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
run: ci/scripts/test.sh . build jni
-
- integration:
- name: AMD64 integration
- runs-on: ubuntu-latest
- timeout-minutes: 60
- steps:
- - name: Checkout Arrow
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- with:
- fetch-depth: 0
- repository: apache/arrow
- submodules: recursive
- - name: Checkout Arrow Rust
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- with:
- repository: apache/arrow-rs
- path: rust
- - name: Checkout Arrow nanoarrow
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- with:
- repository: apache/arrow-nanoarrow
- path: nanoarrow
- - name: Checkout Arrow Go
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- with:
- repository: apache/arrow-go
- path: go
- - name: Checkout Arrow Java
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- with:
- path: java
- - name: Free up disk space
- run: |
- ci/scripts/util_free_space.sh
- - name: Cache Docker Volumes
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
- with:
- path: .docker
- key: integration-conda-${{ hashFiles('cpp/**') }}
- restore-keys: integration-conda-
- - name: Setup Python
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
- with:
- python-version: 3.12
- - name: Setup Archery
- run: pip install -e dev/archery[docker]
- - name: Execute Docker Build
- run: |
- source ci/scripts/util_enable_core_dumps.sh
- archery docker run \
- -e ARCHERY_DEFAULT_BRANCH=main \
- -e ARCHERY_INTEGRATION_TARGET_IMPLEMENTATIONS=java \
- -e ARCHERY_INTEGRATION_WITH_GO=1 \
- -e ARCHERY_INTEGRATION_WITH_JAVA=1 \
- -e ARCHERY_INTEGRATION_WITH_NANOARROW=1 \
- -e ARCHERY_INTEGRATION_WITH_RUST=1 \
- conda-integration
diff --git a/.gitignore b/.gitignore
index b57597af47..17d1d43ae1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,10 +7,12 @@
.buildpath
.checkstyle
.classpath
+.cursor/
.factorypath
.idea/
.project
.settings/
+.vscode/
/*-build/
/.mvn/.develocity/
/apache-arrow-java-*
diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml
index 1dd32d62d6..74482cb2c4 100644
--- a/.mvn/extensions.xml
+++ b/.mvn/extensions.xml
@@ -23,11 +23,11 @@
com.gradledevelocity-maven-extension
- 1.23.1
+ 2.5.0com.gradlecommon-custom-user-data-maven-extension
- 2.0.1
+ 2.3.0
diff --git a/Brewfile b/Brewfile
index af6bd65615..2c47a38af5 100644
--- a/Brewfile
+++ b/Brewfile
@@ -15,5 +15,5 @@
# specific language governing permissions and limitations
# under the License.
-brew "openjdk@11"
+brew "openjdk@17"
brew "sccache"
diff --git a/README.md b/README.md
index c46b61c49e..0196536514 100644
--- a/README.md
+++ b/README.md
@@ -23,11 +23,11 @@
The following guides explain the fundamental data structures used in the Java implementation of Apache Arrow.
-- [ValueVector](https://arrow.apache.org/docs/java/vector.html) is an abstraction that is used to store a sequence of values having the same type in an individual column.
-- [VectorSchemaRoot](https://arrow.apache.org/docs/java/vector_schema_root.html) is a container that can hold multiple vectors based on a schema.
-- The [Reading/Writing IPC formats](https://arrow.apache.org/docs/java/ipc.html) guide explains how to stream record batches as well as serializing record batches to files.
+- [ValueVector](https://arrow.apache.org/java/current/vector.html) is an abstraction that is used to store a sequence of values having the same type in an individual column.
+- [VectorSchemaRoot](https://arrow.apache.org/java/current/vector_schema_root.html#vectorschemaroot) is a container that can hold multiple vectors based on a schema.
+- The [Reading/Writing IPC formats](https://arrow.apache.org/java/current/ipc.html) guide explains how to stream record batches as well as serializing record batches to files.
-Generated javadoc documentation is available [here](https://arrow.apache.org/docs/java/).
+Generated javadoc documentation is available [here](https://arrow.apache.org/java/current/).
## Building from source
@@ -50,7 +50,7 @@ a version of your choosing.
$ flatc --version
flatc version 25.1.24
-$ grep "dep.fbs.version" java/pom.xml
+$ grep "dep.fbs.version" pom.xml
25.1.24
```
@@ -60,10 +60,10 @@ $ grep "dep.fbs.version" java/pom.xml
cd $ARROW_HOME
# remove the existing files
-rm -rf java/format/src
+rm -rf format/src
# regenerate from the .fbs files
-flatc --java -o java/format/src/main/java format/*.fbs
+flatc --java -o format/src/main/java arrow-format/*.fbs
# prepend license header
mvn spotless:apply -pl :arrow-format
@@ -93,7 +93,7 @@ conflicting or duplicate fields set this JVM flag or use the correct static cons
## Java Code Style Guide
-Arrow Java follows the Google style guide [here][3] with the following
+Arrow Java follows the [Google Java Style Guide](http://google.github.io/styleguide/javaguide.html) with the following
differences:
* Imports are grouped, from top to bottom, in this order: static imports,
@@ -119,12 +119,12 @@ following command run in the project root directory:
mvn -Dlogback.configurationFile=file:
```
-See [Logback Configuration][1] for more details.
+See [Logback Configuration](https://logback.qos.ch/manual/configuration.html) for more details.
## Integration Tests
Integration tests which require more time or more memory can be run by activating
-the `integration-tests` profile. This activates the [maven failsafe][4] plugin
+the `integration-tests` profile. This activates the [Maven Failsafe](https://maven.apache.org/surefire/maven-failsafe-plugin/) plugin
and any class prefixed with `IT` will be run during the testing phase. The integration
tests currently require a larger amount of memory (>4GB) and time to complete. To activate
the profile:
@@ -132,8 +132,3 @@ the profile:
```bash
mvn -Pintegration-tests
```
-
-[1]: https://logback.qos.ch/manual/configuration.html
-[2]: https://github.com/apache/arrow/blob/main/cpp/README.md
-[3]: http://google.github.io/styleguide/javaguide.html
-[4]: https://maven.apache.org/surefire/maven-failsafe-plugin/
diff --git a/adapter/avro/pom.xml b/adapter/avro/pom.xml
index 827d19f2a2..4f7f90d7a9 100644
--- a/adapter/avro/pom.xml
+++ b/adapter/avro/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.arrowarrow-java-root
- 19.0.0-SNAPSHOT
+ 20.0.0-SNAPSHOT../../pom.xml
diff --git a/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/ArrowToAvroUtils.java b/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/ArrowToAvroUtils.java
index 87b594af9e..e09b99f670 100644
--- a/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/ArrowToAvroUtils.java
+++ b/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/ArrowToAvroUtils.java
@@ -17,10 +17,14 @@
package org.apache.arrow.adapter.avro;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
import org.apache.arrow.adapter.avro.producers.AvroBigIntProducer;
import org.apache.arrow.adapter.avro.producers.AvroBooleanProducer;
import org.apache.arrow.adapter.avro.producers.AvroBytesProducer;
+import org.apache.arrow.adapter.avro.producers.AvroEnumProducer;
import org.apache.arrow.adapter.avro.producers.AvroFixedSizeBinaryProducer;
import org.apache.arrow.adapter.avro.producers.AvroFixedSizeListProducer;
import org.apache.arrow.adapter.avro.producers.AvroFloat2Producer;
@@ -41,6 +45,7 @@
import org.apache.arrow.adapter.avro.producers.AvroUint8Producer;
import org.apache.arrow.adapter.avro.producers.BaseAvroProducer;
import org.apache.arrow.adapter.avro.producers.CompositeAvroProducer;
+import org.apache.arrow.adapter.avro.producers.DictionaryDecodingProducer;
import org.apache.arrow.adapter.avro.producers.Producer;
import org.apache.arrow.adapter.avro.producers.logical.AvroDateDayProducer;
import org.apache.arrow.adapter.avro.producers.logical.AvroDateMilliProducer;
@@ -59,6 +64,7 @@
import org.apache.arrow.adapter.avro.producers.logical.AvroTimestampSecProducer;
import org.apache.arrow.adapter.avro.producers.logical.AvroTimestampSecTzProducer;
import org.apache.arrow.util.Preconditions;
+import org.apache.arrow.vector.BaseIntVector;
import org.apache.arrow.vector.BigIntVector;
import org.apache.arrow.vector.BitVector;
import org.apache.arrow.vector.DateDayVector;
@@ -96,11 +102,14 @@
import org.apache.arrow.vector.complex.ListVector;
import org.apache.arrow.vector.complex.MapVector;
import org.apache.arrow.vector.complex.StructVector;
+import org.apache.arrow.vector.dictionary.Dictionary;
+import org.apache.arrow.vector.dictionary.DictionaryProvider;
import org.apache.arrow.vector.types.FloatingPointPrecision;
import org.apache.arrow.vector.types.TimeUnit;
import org.apache.arrow.vector.types.Types;
import org.apache.arrow.vector.types.pojo.ArrowType;
import org.apache.arrow.vector.types.pojo.Field;
+import org.apache.arrow.vector.util.Text;
import org.apache.avro.LogicalType;
import org.apache.avro.LogicalTypes;
import org.apache.avro.Schema;
@@ -162,17 +171,29 @@ public class ArrowToAvroUtils {
* may be nullable. Record types must contain at least one child field and cannot contain multiple
* fields with the same name
*
+ *
String fields that are dictionary-encoded will be represented as an Avro enum, so long as
+ * all the values meet the restrictions on Avro enums (non-null, valid identifiers). Other data
+ * types that are dictionary encoded, or string fields that do not meet the avro requirements,
+ * will be output as their decoded type.
+ *
* @param arrowFields The arrow fields used to generate the Avro schema
* @param typeName Name of the top level Avro record type
* @param namespace Namespace of the top level Avro record type
+ * @param dictionaries A dictionary provider is required if any fields use dictionary encoding
* @return An Avro record schema for the given list of fields, with the specified name and
* namespace
*/
public static Schema createAvroSchema(
- List arrowFields, String typeName, String namespace) {
+ List arrowFields, String typeName, String namespace, DictionaryProvider dictionaries) {
SchemaBuilder.RecordBuilder assembler =
SchemaBuilder.record(typeName).namespace(namespace);
- return buildRecordSchema(assembler, arrowFields, namespace);
+ return buildRecordSchema(assembler, arrowFields, namespace, dictionaries);
+ }
+
+ /** Overload provided for convenience, sets dictionaries = null. */
+ public static Schema createAvroSchema(
+ List arrowFields, String typeName, String namespace) {
+ return createAvroSchema(arrowFields, typeName, namespace, null);
}
/** Overload provided for convenience, sets namespace = null. */
@@ -185,61 +206,83 @@ public static Schema createAvroSchema(List arrowFields) {
return createAvroSchema(arrowFields, GENERIC_RECORD_TYPE_NAME);
}
+ /**
+ * Overload provided for convenience, sets name = GENERIC_RECORD_TYPE_NAME and namespace = null.
+ */
+ public static Schema createAvroSchema(List arrowFields, DictionaryProvider dictionaries) {
+ return createAvroSchema(arrowFields, GENERIC_RECORD_TYPE_NAME, null, dictionaries);
+ }
+
private static T buildRecordSchema(
- SchemaBuilder.RecordBuilder builder, List fields, String namespace) {
+ SchemaBuilder.RecordBuilder builder,
+ List fields,
+ String namespace,
+ DictionaryProvider dictionaries) {
if (fields.isEmpty()) {
throw new IllegalArgumentException("Record field must have at least one child field");
}
SchemaBuilder.FieldAssembler assembler = builder.namespace(namespace).fields();
for (Field field : fields) {
- assembler = buildFieldSchema(assembler, field, namespace);
+ assembler = buildFieldSchema(assembler, field, namespace, dictionaries);
}
return assembler.endRecord();
}
private static SchemaBuilder.FieldAssembler buildFieldSchema(
- SchemaBuilder.FieldAssembler assembler, Field field, String namespace) {
+ SchemaBuilder.FieldAssembler assembler,
+ Field field,
+ String namespace,
+ DictionaryProvider dictionaries) {
return assembler
.name(field.getName())
- .type(buildTypeSchema(SchemaBuilder.builder(), field, namespace))
+ .type(buildTypeSchema(SchemaBuilder.builder(), field, namespace, dictionaries))
.noDefault();
}
private static T buildTypeSchema(
- SchemaBuilder.TypeBuilder builder, Field field, String namespace) {
+ SchemaBuilder.TypeBuilder builder,
+ Field field,
+ String namespace,
+ DictionaryProvider dictionaries) {
// Nullable unions need special handling, since union types cannot be directly nested
if (field.getType().getTypeID() == ArrowType.ArrowTypeID.Union) {
boolean unionNullable = field.getChildren().stream().anyMatch(Field::isNullable);
if (unionNullable) {
SchemaBuilder.UnionAccumulator union = builder.unionOf().nullType();
- return addTypesToUnion(union, field.getChildren(), namespace);
+ return addTypesToUnion(union, field.getChildren(), namespace, dictionaries);
} else {
Field headType = field.getChildren().get(0);
List tailTypes = field.getChildren().subList(1, field.getChildren().size());
SchemaBuilder.UnionAccumulator union =
- buildBaseTypeSchema(builder.unionOf(), headType, namespace);
- return addTypesToUnion(union, tailTypes, namespace);
+ buildBaseTypeSchema(builder.unionOf(), headType, namespace, dictionaries);
+ return addTypesToUnion(union, tailTypes, namespace, dictionaries);
}
} else if (field.isNullable()) {
- return buildBaseTypeSchema(builder.nullable(), field, namespace);
+ return buildBaseTypeSchema(builder.nullable(), field, namespace, dictionaries);
} else {
- return buildBaseTypeSchema(builder, field, namespace);
+ return buildBaseTypeSchema(builder, field, namespace, dictionaries);
}
}
private static T buildArraySchema(
- SchemaBuilder.ArrayBuilder builder, Field listField, String namespace) {
+ SchemaBuilder.ArrayBuilder builder,
+ Field listField,
+ String namespace,
+ DictionaryProvider dictionaries) {
if (listField.getChildren().size() != 1) {
throw new IllegalArgumentException("List field must have exactly one child field");
}
Field itemField = listField.getChildren().get(0);
- return buildTypeSchema(builder.items(), itemField, namespace);
+ return buildTypeSchema(builder.items(), itemField, namespace, dictionaries);
}
private static T buildMapSchema(
- SchemaBuilder.MapBuilder builder, Field mapField, String namespace) {
+ SchemaBuilder.MapBuilder builder,
+ Field mapField,
+ String namespace,
+ DictionaryProvider dictionaries) {
if (mapField.getChildren().size() != 1) {
throw new IllegalArgumentException("Map field must have exactly one child field");
}
@@ -253,11 +296,14 @@ private static T buildMapSchema(
throw new IllegalArgumentException(
"Map keys must be of type string and cannot be nullable for conversion to Avro");
}
- return buildTypeSchema(builder.values(), valueField, namespace);
+ return buildTypeSchema(builder.values(), valueField, namespace, dictionaries);
}
private static T buildBaseTypeSchema(
- SchemaBuilder.BaseTypeBuilder builder, Field field, String namespace) {
+ SchemaBuilder.BaseTypeBuilder builder,
+ Field field,
+ String namespace,
+ DictionaryProvider dictionaries) {
ArrowType.ArrowTypeID typeID = field.getType().getTypeID();
@@ -269,6 +315,33 @@ private static T buildBaseTypeSchema(
return builder.booleanType();
case Int:
+ if (field.getDictionary() != null) {
+ if (dictionaries == null) {
+ throw new IllegalArgumentException(
+ "Field references a dictionary but no dictionaries were provided: "
+ + field.getName());
+ }
+ Dictionary dictionary = dictionaries.lookup(field.getDictionary().getId());
+ if (dictionary == null) {
+ throw new IllegalArgumentException(
+ "Field references a dictionary that does not exist: "
+ + field.getName()
+ + ", dictionary ID = "
+ + field.getDictionary().getId());
+ }
+ if (dictionaryIsValidEnum(dictionary)) {
+ String[] symbols = dictionarySymbols(dictionary);
+ return builder.enumeration(field.getName()).symbols(symbols);
+ } else {
+ Field decodedField =
+ new Field(
+ field.getName(),
+ dictionary.getVector().getField().getFieldType(),
+ dictionary.getVector().getField().getChildren());
+ return buildBaseTypeSchema(builder, decodedField, namespace, dictionaries);
+ }
+ }
+
ArrowType.Int intType = (ArrowType.Int) field.getType();
if (intType.getBitWidth() > 32 || (intType.getBitWidth() == 32 && !intType.getIsSigned())) {
return builder.longType();
@@ -328,7 +401,7 @@ private static T buildBaseTypeSchema(
String childNamespace =
namespace == null ? field.getName() : namespace + "." + field.getName();
return buildRecordSchema(
- builder.record(field.getName()), field.getChildren(), childNamespace);
+ builder.record(field.getName()), field.getChildren(), childNamespace, dictionaries);
case List:
case FixedSizeList:
@@ -339,13 +412,13 @@ private static T buildBaseTypeSchema(
new Field("item", itemField.getFieldType(), itemField.getChildren());
Field safeListField =
new Field(field.getName(), field.getFieldType(), List.of(safeItemField));
- return buildArraySchema(builder.array(), safeListField, namespace);
+ return buildArraySchema(builder.array(), safeListField, namespace, dictionaries);
} else {
- return buildArraySchema(builder.array(), field, namespace);
+ return buildArraySchema(builder.array(), field, namespace, dictionaries);
}
case Map:
- return buildMapSchema(builder.map(), field, namespace);
+ return buildMapSchema(builder.map(), field, namespace, dictionaries);
default:
throw new IllegalArgumentException(
@@ -354,9 +427,12 @@ private static T buildBaseTypeSchema(
}
private static T addTypesToUnion(
- SchemaBuilder.UnionAccumulator accumulator, List unionFields, String namespace) {
+ SchemaBuilder.UnionAccumulator accumulator,
+ List unionFields,
+ String namespace,
+ DictionaryProvider dictionaries) {
for (var field : unionFields) {
- accumulator = buildBaseTypeSchema(accumulator.and(), field, namespace);
+ accumulator = buildBaseTypeSchema(accumulator.and(), field, namespace, dictionaries);
}
return accumulator.endUnion();
}
@@ -373,30 +449,88 @@ private static LogicalType timestampLogicalType(ArrowType.Timestamp timestampTyp
}
}
+ private static boolean dictionaryIsValidEnum(Dictionary dictionary) {
+
+ if (dictionary.getVectorType().getTypeID() != ArrowType.ArrowTypeID.Utf8) {
+ return false;
+ }
+
+ VarCharVector vector = (VarCharVector) dictionary.getVector();
+ Set symbols = new HashSet<>();
+
+ for (int i = 0; i < vector.getValueCount(); i++) {
+ if (vector.isNull(i)) {
+ return false;
+ }
+ Text text = vector.getObject(i);
+ if (text == null) {
+ return false;
+ }
+ String symbol = text.toString();
+ if (!ENUM_REGEX.matcher(symbol).matches()) {
+ return false;
+ }
+ if (symbols.contains(symbol)) {
+ return false;
+ }
+ symbols.add(symbol);
+ }
+
+ return true;
+ }
+
+ private static String[] dictionarySymbols(Dictionary dictionary) {
+
+ VarCharVector vector = (VarCharVector) dictionary.getVector();
+ String[] symbols = new String[vector.getValueCount()];
+
+ for (int i = 0; i < vector.getValueCount(); i++) {
+ Text text = vector.getObject(i);
+ // This should never happen if dictionaryIsValidEnum() succeeded
+ if (text == null) {
+ throw new IllegalArgumentException("Illegal null value in enum");
+ }
+ symbols[i] = text.toString();
+ }
+
+ return symbols;
+ }
+
+ private static final Pattern ENUM_REGEX = Pattern.compile("^[A-Za-z_][A-Za-z0-9_]*$");
+
/**
* Create a composite Avro producer for a set of field vectors (typically the root set of a VSR).
*
* @param vectors The vectors that will be used to produce Avro data
* @return The resulting composite Avro producer
*/
- public static CompositeAvroProducer createCompositeProducer(List vectors) {
+ public static CompositeAvroProducer createCompositeProducer(
+ List vectors, DictionaryProvider dictionaries) {
List> producers = new ArrayList<>(vectors.size());
for (FieldVector vector : vectors) {
- BaseAvroProducer extends FieldVector> producer = createProducer(vector);
+ BaseAvroProducer extends FieldVector> producer = createProducer(vector, dictionaries);
producers.add(producer);
}
return new CompositeAvroProducer(producers);
}
- private static BaseAvroProducer> createProducer(FieldVector vector) {
+ /** Overload provided for convenience, sets dictionaries = null. */
+ public static CompositeAvroProducer createCompositeProducer(List vectors) {
+
+ return createCompositeProducer(vectors, null);
+ }
+
+ private static BaseAvroProducer> createProducer(
+ FieldVector vector, DictionaryProvider dictionaries) {
boolean nullable = vector.getField().isNullable();
- return createProducer(vector, nullable);
+ return createProducer(vector, nullable, dictionaries);
}
- private static BaseAvroProducer> createProducer(FieldVector vector, boolean nullable) {
+ private static BaseAvroProducer> createProducer(
+ FieldVector vector, boolean nullable, DictionaryProvider dictionaries) {
Preconditions.checkNotNull(vector, "Arrow vector object can't be null");
@@ -405,10 +539,34 @@ private static BaseAvroProducer> createProducer(FieldVector vector, boolean nu
// Avro understands nullable types as a union of type | null
// Most nullable fields in a VSR will not be unions, so provide a special wrapper
if (nullable && minorType != Types.MinorType.UNION) {
- final BaseAvroProducer> innerProducer = createProducer(vector, false);
+ final BaseAvroProducer> innerProducer = createProducer(vector, false, dictionaries);
return new AvroNullableProducer<>(innerProducer);
}
+ if (vector.getField().getDictionary() != null) {
+ if (dictionaries == null) {
+ throw new IllegalArgumentException(
+ "Field references a dictionary but no dictionaries were provided: "
+ + vector.getField().getName());
+ }
+ Dictionary dictionary = dictionaries.lookup(vector.getField().getDictionary().getId());
+ if (dictionary == null) {
+ throw new IllegalArgumentException(
+ "Field references a dictionary that does not exist: "
+ + vector.getField().getName()
+ + ", dictionary ID = "
+ + vector.getField().getDictionary().getId());
+ }
+ // If a field is dictionary-encoded but cannot be represented as an Avro enum,
+ // then decode it before writing
+ if (dictionaryIsValidEnum(dictionary)) {
+ return new AvroEnumProducer((BaseIntVector) vector);
+ } else {
+ BaseAvroProducer> dictProducer = createProducer(dictionary.getVector(), false, null);
+ return new DictionaryDecodingProducer<>((BaseIntVector) vector, dictProducer);
+ }
+ }
+
switch (minorType) {
case NULL:
return new AvroNullProducer((NullVector) vector);
@@ -486,21 +644,23 @@ private static BaseAvroProducer> createProducer(FieldVector vector, boolean nu
Producer>[] childProducers = new Producer>[childVectors.size()];
for (int i = 0; i < childVectors.size(); i++) {
FieldVector childVector = childVectors.get(i);
- childProducers[i] = createProducer(childVector, childVector.getField().isNullable());
+ childProducers[i] =
+ createProducer(childVector, childVector.getField().isNullable(), dictionaries);
}
return new AvroStructProducer(structVector, childProducers);
case LIST:
ListVector listVector = (ListVector) vector;
FieldVector itemVector = listVector.getDataVector();
- Producer> itemProducer = createProducer(itemVector, itemVector.getField().isNullable());
+ Producer> itemProducer =
+ createProducer(itemVector, itemVector.getField().isNullable(), dictionaries);
return new AvroListProducer(listVector, itemProducer);
case FIXED_SIZE_LIST:
FixedSizeListVector fixedListVector = (FixedSizeListVector) vector;
FieldVector fixedItemVector = fixedListVector.getDataVector();
Producer> fixedItemProducer =
- createProducer(fixedItemVector, fixedItemVector.getField().isNullable());
+ createProducer(fixedItemVector, fixedItemVector.getField().isNullable(), dictionaries);
return new AvroFixedSizeListProducer(fixedListVector, fixedItemProducer);
case MAP:
@@ -514,7 +674,7 @@ private static BaseAvroProducer> createProducer(FieldVector vector, boolean nu
FieldVector valueVector = entryVector.getChildrenFromFields().get(1);
Producer> keyProducer = new AvroStringProducer(keyVector);
Producer> valueProducer =
- createProducer(valueVector, valueVector.getField().isNullable());
+ createProducer(valueVector, valueVector.getField().isNullable(), dictionaries);
Producer> entryProducer =
new AvroStructProducer(entryVector, new Producer>[] {keyProducer, valueProducer});
return new AvroMapProducer(mapVector, entryProducer);
diff --git a/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/AvroToArrowUtils.java b/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/AvroToArrowUtils.java
index aedef7732e..a6e77e4050 100644
--- a/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/AvroToArrowUtils.java
+++ b/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/AvroToArrowUtils.java
@@ -1071,8 +1071,8 @@ private static FieldType createFieldType(
}
private static String convertAliases(Set aliases) {
- JsonStringArrayList jsonList = new JsonStringArrayList();
- aliases.stream().forEach(a -> jsonList.add(a));
+ JsonStringArrayList jsonList = new JsonStringArrayList(aliases.size());
+ jsonList.addAll(aliases);
return jsonList.toString();
}
}
diff --git a/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/AvroToArrowVectorIterator.java b/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/AvroToArrowVectorIterator.java
index 4123370061..e82fdc36fb 100644
--- a/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/AvroToArrowVectorIterator.java
+++ b/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/AvroToArrowVectorIterator.java
@@ -17,13 +17,14 @@
package org.apache.arrow.adapter.avro;
import java.io.EOFException;
-import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.arrow.adapter.avro.consumers.CompositeAvroConsumer;
+import org.apache.arrow.adapter.avro.consumers.Consumer;
import org.apache.arrow.util.Preconditions;
import org.apache.arrow.vector.FieldVector;
+import org.apache.arrow.vector.ValueVector;
import org.apache.arrow.vector.VectorSchemaRoot;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.util.ValueVectorUtility;
@@ -75,9 +76,11 @@ public static AvroToArrowVectorIterator create(
private void initialize() {
// create consumers
compositeConsumer = AvroToArrowUtils.createCompositeConsumer(schema, config);
- List vectors = new ArrayList<>();
- compositeConsumer.getConsumers().forEach(c -> vectors.add(c.getVector()));
- List fields = vectors.stream().map(t -> t.getField()).collect(Collectors.toList());
+ List vectors =
+ compositeConsumer.getConsumers().stream()
+ .map(Consumer::getVector)
+ .collect(Collectors.toList());
+ List fields = vectors.stream().map(ValueVector::getField).collect(Collectors.toList());
VectorSchemaRoot root = new VectorSchemaRoot(fields, vectors, 0);
rootSchema = root.getSchema();
diff --git a/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/producers/AvroEnumProducer.java b/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/producers/AvroEnumProducer.java
index 068566493e..eebfb7d241 100644
--- a/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/producers/AvroEnumProducer.java
+++ b/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/producers/AvroEnumProducer.java
@@ -17,22 +17,22 @@
package org.apache.arrow.adapter.avro.producers;
import java.io.IOException;
-import org.apache.arrow.vector.IntVector;
+import org.apache.arrow.vector.BaseIntVector;
import org.apache.avro.io.Encoder;
/**
- * Producer that produces enum values from a dictionary-encoded {@link IntVector}, writes data to an
- * Avro encoder.
+ * Producer that produces enum values from a dictionary-encoded {@link BaseIntVector}, writes data
+ * to an Avro encoder.
*/
-public class AvroEnumProducer extends BaseAvroProducer {
+public class AvroEnumProducer extends BaseAvroProducer {
/** Instantiate an AvroEnumProducer. */
- public AvroEnumProducer(IntVector vector) {
+ public AvroEnumProducer(BaseIntVector vector) {
super(vector);
}
@Override
public void produce(Encoder encoder) throws IOException {
- encoder.writeEnum(vector.get(currentIndex++));
+ encoder.writeEnum((int) vector.getValueAsLong(currentIndex++));
}
}
diff --git a/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/producers/DictionaryDecodingProducer.java b/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/producers/DictionaryDecodingProducer.java
new file mode 100644
index 0000000000..afeba08511
--- /dev/null
+++ b/adapter/avro/src/main/java/org/apache/arrow/adapter/avro/producers/DictionaryDecodingProducer.java
@@ -0,0 +1,47 @@
+/*
+ * 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.adapter.avro.producers;
+
+import java.io.IOException;
+import org.apache.arrow.vector.BaseIntVector;
+import org.apache.arrow.vector.FieldVector;
+import org.apache.avro.io.Encoder;
+
+/**
+ * Producer that decodes values from a dictionary-encoded {@link FieldVector}, writes the resulting
+ * values to an Avro encoder.
+ *
+ * @param Type of the underlying dictionary vector
+ */
+public class DictionaryDecodingProducer
+ extends BaseAvroProducer {
+
+ private final Producer dictProducer;
+
+ /** Instantiate a DictionaryDecodingProducer. */
+ public DictionaryDecodingProducer(BaseIntVector indexVector, Producer dictProducer) {
+ super(indexVector);
+ this.dictProducer = dictProducer;
+ }
+
+ @Override
+ public void produce(Encoder encoder) throws IOException {
+ int dicIndex = (int) vector.getValueAsLong(currentIndex++);
+ dictProducer.setPosition(dicIndex);
+ dictProducer.produce(encoder);
+ }
+}
diff --git a/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/ArrowToAvroDataTest.java b/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/ArrowToAvroDataTest.java
index 2d70b45021..6d66ee9d45 100644
--- a/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/ArrowToAvroDataTest.java
+++ b/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/ArrowToAvroDataTest.java
@@ -76,10 +76,14 @@
import org.apache.arrow.vector.complex.StructVector;
import org.apache.arrow.vector.complex.writer.BaseWriter;
import org.apache.arrow.vector.complex.writer.FieldWriter;
+import org.apache.arrow.vector.dictionary.Dictionary;
+import org.apache.arrow.vector.dictionary.DictionaryEncoder;
+import org.apache.arrow.vector.dictionary.DictionaryProvider;
import org.apache.arrow.vector.types.DateUnit;
import org.apache.arrow.vector.types.FloatingPointPrecision;
import org.apache.arrow.vector.types.TimeUnit;
import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.DictionaryEncoding;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.FieldType;
import org.apache.arrow.vector.util.JsonStringArrayList;
@@ -2817,4 +2821,81 @@ record = datumReader.read(record, decoder);
}
}
}
+
+ @Test
+ public void testWriteDictEnumEncoded() throws Exception {
+
+ BufferAllocator allocator = new RootAllocator();
+
+ // Create a dictionary
+ FieldType dictionaryField = new FieldType(false, new ArrowType.Utf8(), null);
+ VarCharVector dictionaryVector =
+ new VarCharVector(new Field("dictionary", dictionaryField, null), allocator);
+
+ dictionaryVector.allocateNew(3);
+ dictionaryVector.set(0, "apple".getBytes());
+ dictionaryVector.set(1, "banana".getBytes());
+ dictionaryVector.set(2, "cherry".getBytes());
+ dictionaryVector.setValueCount(3);
+
+ Dictionary dictionary =
+ new Dictionary(dictionaryVector, new DictionaryEncoding(1L, false, null));
+ DictionaryProvider dictionaries = new DictionaryProvider.MapDictionaryProvider(dictionary);
+
+ // Field definition
+ FieldType stringField = new FieldType(false, new ArrowType.Utf8(), null);
+ VarCharVector stringVector =
+ new VarCharVector(new Field("enumField", stringField, null), allocator);
+ stringVector.allocateNew(10);
+ stringVector.setSafe(0, "apple".getBytes());
+ stringVector.setSafe(1, "banana".getBytes());
+ stringVector.setSafe(2, "cherry".getBytes());
+ stringVector.setSafe(3, "cherry".getBytes());
+ stringVector.setSafe(4, "apple".getBytes());
+ stringVector.setSafe(5, "banana".getBytes());
+ stringVector.setSafe(6, "apple".getBytes());
+ stringVector.setSafe(7, "cherry".getBytes());
+ stringVector.setSafe(8, "banana".getBytes());
+ stringVector.setSafe(9, "apple".getBytes());
+ stringVector.setValueCount(10);
+
+ IntVector encodedVector = (IntVector) DictionaryEncoder.encode(stringVector, dictionary);
+
+ // Set up VSR
+ List vectors = Arrays.asList(encodedVector);
+ int rowCount = 10;
+
+ try (VectorSchemaRoot root = new VectorSchemaRoot(vectors)) {
+
+ File dataFile = new File(TMP, "testWriteEnumEncoded.avro");
+
+ // Write an AVRO block using the producer classes
+ try (FileOutputStream fos = new FileOutputStream(dataFile)) {
+ BinaryEncoder encoder = new EncoderFactory().directBinaryEncoder(fos, null);
+ CompositeAvroProducer producer =
+ ArrowToAvroUtils.createCompositeProducer(vectors, dictionaries);
+ for (int row = 0; row < rowCount; row++) {
+ producer.produce(encoder);
+ }
+ encoder.flush();
+ }
+
+ // Set up reading the AVRO block as a GenericRecord
+ Schema schema = ArrowToAvroUtils.createAvroSchema(root.getSchema().getFields(), dictionaries);
+ GenericDatumReader datumReader = new GenericDatumReader<>(schema);
+
+ try (InputStream inputStream = new FileInputStream(dataFile)) {
+
+ BinaryDecoder decoder = DecoderFactory.get().binaryDecoder(inputStream, null);
+ GenericRecord record = null;
+
+ // Read and check values
+ for (int row = 0; row < rowCount; row++) {
+ record = datumReader.read(record, decoder);
+ // Values read from Avro should be the decoded enum values
+ assertEquals(stringVector.getObject(row).toString(), record.get("enumField").toString());
+ }
+ }
+ }
+ }
}
diff --git a/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/ArrowToAvroSchemaTest.java b/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/ArrowToAvroSchemaTest.java
index d3e12e763a..d5e0357a8c 100644
--- a/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/ArrowToAvroSchemaTest.java
+++ b/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/ArrowToAvroSchemaTest.java
@@ -20,11 +20,18 @@
import java.util.Arrays;
import java.util.List;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.vector.BigIntVector;
+import org.apache.arrow.vector.VarCharVector;
+import org.apache.arrow.vector.dictionary.Dictionary;
+import org.apache.arrow.vector.dictionary.DictionaryProvider;
import org.apache.arrow.vector.types.DateUnit;
import org.apache.arrow.vector.types.FloatingPointPrecision;
import org.apache.arrow.vector.types.TimeUnit;
import org.apache.arrow.vector.types.UnionMode;
import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.DictionaryEncoding;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.FieldType;
import org.apache.avro.LogicalTypes;
@@ -1389,4 +1396,126 @@ public void testConvertUnionTypes() {
Schema.Type.STRING,
schema.getField("nullableDenseUnionField").schema().getTypes().get(3).getType());
}
+
+ @Test
+ public void testWriteDictEnumEncoded() {
+
+ BufferAllocator allocator = new RootAllocator();
+
+ // Create a dictionary
+ FieldType dictionaryField = new FieldType(false, new ArrowType.Utf8(), null);
+ VarCharVector dictionaryVector =
+ new VarCharVector(new Field("dictionary", dictionaryField, null), allocator);
+
+ dictionaryVector.allocateNew(3);
+ dictionaryVector.set(0, "apple".getBytes());
+ dictionaryVector.set(1, "banana".getBytes());
+ dictionaryVector.set(2, "cherry".getBytes());
+ dictionaryVector.setValueCount(3);
+
+ Dictionary dictionary =
+ new Dictionary(
+ dictionaryVector, new DictionaryEncoding(0L, false, new ArrowType.Int(8, true)));
+ DictionaryProvider dictionaries = new DictionaryProvider.MapDictionaryProvider(dictionary);
+
+ List fields =
+ Arrays.asList(
+ new Field(
+ "enumField",
+ new FieldType(false, new ArrowType.Int(8, true), dictionary.getEncoding(), null),
+ null));
+
+ Schema schema = ArrowToAvroUtils.createAvroSchema(fields, "TestRecord", null, dictionaries);
+
+ assertEquals(Schema.Type.RECORD, schema.getType());
+ assertEquals(1, schema.getFields().size());
+
+ Schema.Field enumField = schema.getField("enumField");
+
+ assertEquals(Schema.Type.ENUM, enumField.schema().getType());
+ assertEquals(3, enumField.schema().getEnumSymbols().size());
+ assertEquals("apple", enumField.schema().getEnumSymbols().get(0));
+ assertEquals("banana", enumField.schema().getEnumSymbols().get(1));
+ assertEquals("cherry", enumField.schema().getEnumSymbols().get(2));
+ }
+
+ @Test
+ public void testWriteDictEnumInvalid() {
+
+ BufferAllocator allocator = new RootAllocator();
+
+ // Create a dictionary
+ FieldType dictionaryField = new FieldType(false, new ArrowType.Utf8(), null);
+ VarCharVector dictionaryVector =
+ new VarCharVector(new Field("dictionary", dictionaryField, null), allocator);
+
+ dictionaryVector.allocateNew(3);
+ dictionaryVector.set(0, "passion fruit".getBytes());
+ dictionaryVector.set(1, "banana".getBytes());
+ dictionaryVector.set(2, "cherry".getBytes());
+ dictionaryVector.setValueCount(3);
+
+ Dictionary dictionary =
+ new Dictionary(
+ dictionaryVector, new DictionaryEncoding(0L, false, new ArrowType.Int(8, true)));
+ DictionaryProvider dictionaries = new DictionaryProvider.MapDictionaryProvider(dictionary);
+
+ List fields =
+ Arrays.asList(
+ new Field(
+ "enumField",
+ new FieldType(false, new ArrowType.Int(8, true), dictionary.getEncoding(), null),
+ null));
+
+ // Dictionary field contains values that are not valid enums
+ // Should be decoded and output as a string field
+
+ Schema schema = ArrowToAvroUtils.createAvroSchema(fields, "TestRecord", null, dictionaries);
+
+ assertEquals(Schema.Type.RECORD, schema.getType());
+ assertEquals(1, schema.getFields().size());
+
+ Schema.Field enumField = schema.getField("enumField");
+ assertEquals(Schema.Type.STRING, enumField.schema().getType());
+ }
+
+ @Test
+ public void testWriteDictEnumInvalid2() {
+
+ BufferAllocator allocator = new RootAllocator();
+
+ // Create a dictionary
+ FieldType dictionaryField = new FieldType(false, new ArrowType.Int(64, true), null);
+ BigIntVector dictionaryVector =
+ new BigIntVector(new Field("dictionary", dictionaryField, null), allocator);
+
+ dictionaryVector.allocateNew(3);
+ dictionaryVector.set(0, 123L);
+ dictionaryVector.set(1, 456L);
+ dictionaryVector.set(2, 789L);
+ dictionaryVector.setValueCount(3);
+
+ Dictionary dictionary =
+ new Dictionary(
+ dictionaryVector, new DictionaryEncoding(0L, false, new ArrowType.Int(8, true)));
+ DictionaryProvider dictionaries = new DictionaryProvider.MapDictionaryProvider(dictionary);
+
+ List fields =
+ Arrays.asList(
+ new Field(
+ "enumField",
+ new FieldType(false, new ArrowType.Int(8, true), dictionary.getEncoding(), null),
+ null));
+
+ // Dictionary field encodes LONG values rather than STRING
+ // Should be doecded and output as a LONG field
+
+ Schema schema = ArrowToAvroUtils.createAvroSchema(fields, "TestRecord", null, dictionaries);
+
+ assertEquals(Schema.Type.RECORD, schema.getType());
+ assertEquals(1, schema.getFields().size());
+
+ Schema.Field enumField = schema.getField("enumField");
+ assertEquals(Schema.Type.LONG, enumField.schema().getType());
+ }
}
diff --git a/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/RoundTripDataTest.java b/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/RoundTripDataTest.java
index 85e6a960b0..ceaf59aa72 100644
--- a/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/RoundTripDataTest.java
+++ b/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/RoundTripDataTest.java
@@ -52,6 +52,7 @@
import org.apache.arrow.vector.TimeStampMilliVector;
import org.apache.arrow.vector.TimeStampNanoTZVector;
import org.apache.arrow.vector.TimeStampNanoVector;
+import org.apache.arrow.vector.TinyIntVector;
import org.apache.arrow.vector.VarBinaryVector;
import org.apache.arrow.vector.VarCharVector;
import org.apache.arrow.vector.VectorSchemaRoot;
@@ -60,10 +61,14 @@
import org.apache.arrow.vector.complex.StructVector;
import org.apache.arrow.vector.complex.writer.BaseWriter;
import org.apache.arrow.vector.complex.writer.FieldWriter;
+import org.apache.arrow.vector.dictionary.Dictionary;
+import org.apache.arrow.vector.dictionary.DictionaryEncoder;
+import org.apache.arrow.vector.dictionary.DictionaryProvider;
import org.apache.arrow.vector.types.DateUnit;
import org.apache.arrow.vector.types.FloatingPointPrecision;
import org.apache.arrow.vector.types.TimeUnit;
import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.DictionaryEncoding;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.FieldType;
import org.apache.avro.Schema;
@@ -78,16 +83,21 @@ public class RoundTripDataTest {
@TempDir public static File TMP;
- private static AvroToArrowConfig basicConfig(BufferAllocator allocator) {
- return new AvroToArrowConfig(allocator, 1000, null, Collections.emptySet(), false);
+ private static AvroToArrowConfig basicConfig(
+ BufferAllocator allocator, DictionaryProvider.MapDictionaryProvider dictionaries) {
+ return new AvroToArrowConfig(allocator, 1000, dictionaries, Collections.emptySet(), false);
}
private static VectorSchemaRoot readDataFile(
- Schema schema, File dataFile, BufferAllocator allocator) throws Exception {
+ Schema schema,
+ File dataFile,
+ BufferAllocator allocator,
+ DictionaryProvider.MapDictionaryProvider dictionaries)
+ throws Exception {
try (FileInputStream fis = new FileInputStream(dataFile)) {
BinaryDecoder decoder = new DecoderFactory().directBinaryDecoder(fis, null);
- return AvroToArrow.avroToArrow(schema, decoder, basicConfig(allocator));
+ return AvroToArrow.avroToArrow(schema, decoder, basicConfig(allocator, dictionaries));
}
}
@@ -95,11 +105,22 @@ private static void roundTripTest(
VectorSchemaRoot root, BufferAllocator allocator, File dataFile, int rowCount)
throws Exception {
+ roundTripTest(root, allocator, dataFile, rowCount, null);
+ }
+
+ private static void roundTripTest(
+ VectorSchemaRoot root,
+ BufferAllocator allocator,
+ File dataFile,
+ int rowCount,
+ DictionaryProvider dictionaries)
+ throws Exception {
+
// Write an AVRO block using the producer classes
try (FileOutputStream fos = new FileOutputStream(dataFile)) {
BinaryEncoder encoder = new EncoderFactory().directBinaryEncoder(fos, null);
CompositeAvroProducer producer =
- ArrowToAvroUtils.createCompositeProducer(root.getFieldVectors());
+ ArrowToAvroUtils.createCompositeProducer(root.getFieldVectors(), dictionaries);
for (int row = 0; row < rowCount; row++) {
producer.produce(encoder);
}
@@ -107,10 +128,14 @@ private static void roundTripTest(
}
// Generate AVRO schema
- Schema schema = ArrowToAvroUtils.createAvroSchema(root.getSchema().getFields());
+ Schema schema = ArrowToAvroUtils.createAvroSchema(root.getSchema().getFields(), dictionaries);
+
+ DictionaryProvider.MapDictionaryProvider roundTripDictionaries =
+ new DictionaryProvider.MapDictionaryProvider();
// Read back in and compare
- try (VectorSchemaRoot roundTrip = readDataFile(schema, dataFile, allocator)) {
+ try (VectorSchemaRoot roundTrip =
+ readDataFile(schema, dataFile, allocator, roundTripDictionaries)) {
assertEquals(root.getSchema(), roundTrip.getSchema());
assertEquals(rowCount, roundTrip.getRowCount());
@@ -119,6 +144,21 @@ private static void roundTripTest(
for (int row = 0; row < rowCount; row++) {
assertEquals(root.getVector(0).getObject(row), roundTrip.getVector(0).getObject(row));
}
+
+ if (dictionaries != null) {
+ for (long id : dictionaries.getDictionaryIds()) {
+ Dictionary originalDictionary = dictionaries.lookup(id);
+ Dictionary roundTripDictionary = roundTripDictionaries.lookup(id);
+ assertEquals(
+ originalDictionary.getVector().getValueCount(),
+ roundTripDictionary.getVector().getValueCount());
+ for (int j = 0; j < originalDictionary.getVector().getValueCount(); j++) {
+ assertEquals(
+ originalDictionary.getVector().getObject(j),
+ roundTripDictionary.getVector().getObject(j));
+ }
+ }
+ }
}
}
@@ -141,7 +181,7 @@ private static void roundTripByteArrayTest(
Schema schema = ArrowToAvroUtils.createAvroSchema(root.getSchema().getFields());
// Read back in and compare
- try (VectorSchemaRoot roundTrip = readDataFile(schema, dataFile, allocator)) {
+ try (VectorSchemaRoot roundTrip = readDataFile(schema, dataFile, allocator, null)) {
assertEquals(root.getSchema(), roundTrip.getSchema());
assertEquals(rowCount, roundTrip.getRowCount());
@@ -1603,4 +1643,58 @@ public void testRoundTripNullableStructs() throws Exception {
roundTripTest(root, allocator, dataFile, rowCount);
}
}
+
+ @Test
+ public void testRoundTripEnum() throws Exception {
+
+ BufferAllocator allocator = new RootAllocator();
+
+ // Create a dictionary
+ FieldType dictionaryField = new FieldType(false, new ArrowType.Utf8(), null);
+ VarCharVector dictionaryVector =
+ new VarCharVector(new Field("dictionary", dictionaryField, null), allocator);
+
+ dictionaryVector.allocateNew(3);
+ dictionaryVector.set(0, "apple".getBytes());
+ dictionaryVector.set(1, "banana".getBytes());
+ dictionaryVector.set(2, "cherry".getBytes());
+ dictionaryVector.setValueCount(3);
+
+ // For simplicity, ensure the index type matches what will be decoded during Avro enum decoding
+ Dictionary dictionary =
+ new Dictionary(
+ dictionaryVector, new DictionaryEncoding(0L, false, new ArrowType.Int(8, true)));
+ DictionaryProvider dictionaries = new DictionaryProvider.MapDictionaryProvider(dictionary);
+
+ // Field definition
+ FieldType stringField = new FieldType(false, new ArrowType.Utf8(), null);
+ VarCharVector stringVector =
+ new VarCharVector(new Field("enumField", stringField, null), allocator);
+ stringVector.allocateNew(10);
+ stringVector.setSafe(0, "apple".getBytes());
+ stringVector.setSafe(1, "banana".getBytes());
+ stringVector.setSafe(2, "cherry".getBytes());
+ stringVector.setSafe(3, "cherry".getBytes());
+ stringVector.setSafe(4, "apple".getBytes());
+ stringVector.setSafe(5, "banana".getBytes());
+ stringVector.setSafe(6, "apple".getBytes());
+ stringVector.setSafe(7, "cherry".getBytes());
+ stringVector.setSafe(8, "banana".getBytes());
+ stringVector.setSafe(9, "apple".getBytes());
+ stringVector.setValueCount(10);
+
+ TinyIntVector encodedVector =
+ (TinyIntVector) DictionaryEncoder.encode(stringVector, dictionary);
+
+ // Set up VSR
+ List vectors = Arrays.asList(encodedVector);
+ int rowCount = 10;
+
+ try (VectorSchemaRoot root = new VectorSchemaRoot(vectors)) {
+
+ File dataFile = new File(TMP, "testRoundTripEnums.avro");
+
+ roundTripTest(root, allocator, dataFile, rowCount, dictionaries);
+ }
+ }
}
diff --git a/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/RoundTripSchemaTest.java b/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/RoundTripSchemaTest.java
index 864e2c8b59..37c0b4d9fe 100644
--- a/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/RoundTripSchemaTest.java
+++ b/adapter/avro/src/test/java/org/apache/arrow/adapter/avro/RoundTripSchemaTest.java
@@ -21,27 +21,50 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.vector.VarCharVector;
+import org.apache.arrow.vector.dictionary.Dictionary;
+import org.apache.arrow.vector.dictionary.DictionaryProvider;
import org.apache.arrow.vector.types.DateUnit;
import org.apache.arrow.vector.types.FloatingPointPrecision;
import org.apache.arrow.vector.types.TimeUnit;
import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.DictionaryEncoding;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.FieldType;
import org.apache.avro.Schema;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class RoundTripSchemaTest {
private void doRoundTripTest(List fields) {
+ doRoundTripTest(fields, null);
+ }
- AvroToArrowConfig config = new AvroToArrowConfig(null, 1, null, Collections.emptySet(), false);
+ private void doRoundTripTest(List fields, DictionaryProvider dictionaries) {
- Schema avroSchema = ArrowToAvroUtils.createAvroSchema(fields, "TestRecord");
+ DictionaryProvider.MapDictionaryProvider decodeDictionaries =
+ new DictionaryProvider.MapDictionaryProvider();
+ AvroToArrowConfig decodeConfig =
+ new AvroToArrowConfig(null, 1, decodeDictionaries, Collections.emptySet(), false);
+
+ Schema avroSchema = ArrowToAvroUtils.createAvroSchema(fields, "TestRecord", null, dictionaries);
org.apache.arrow.vector.types.pojo.Schema arrowSchema =
- AvroToArrowUtils.createArrowSchema(avroSchema, config);
+ AvroToArrowUtils.createArrowSchema(avroSchema, decodeConfig);
// Compare string representations - equality not defined for logical types
assertEquals(fields, arrowSchema.getFields());
+
+ for (int i = 0; i < fields.size(); i++) {
+ Field field = fields.get(i);
+ Field rtField = arrowSchema.getFields().get(i);
+ if (field.getDictionary() != null) {
+ // Dictionary content is not decoded until the data is consumed
+ Assertions.assertNotNull(rtField.getDictionary());
+ }
+ }
}
// Schema round trip for primitive types, nullable and non-nullable
@@ -440,4 +463,38 @@ public void testRoundTripStructType() {
doRoundTripTest(fields);
}
+
+ @Test
+ public void testRoundTripEnumType() {
+
+ BufferAllocator allocator = new RootAllocator();
+
+ FieldType dictionaryField = new FieldType(false, new ArrowType.Utf8(), null);
+ VarCharVector dictionaryVector =
+ new VarCharVector(new Field("dictionary", dictionaryField, null), allocator);
+
+ dictionaryVector.allocateNew(3);
+ dictionaryVector.set(0, "apple".getBytes());
+ dictionaryVector.set(1, "banana".getBytes());
+ dictionaryVector.set(2, "cherry".getBytes());
+ dictionaryVector.setValueCount(3);
+
+ // For simplicity, ensure the index type matches what will be decoded during Avro enum decoding
+ Dictionary dictionary =
+ new Dictionary(
+ dictionaryVector, new DictionaryEncoding(0L, false, new ArrowType.Int(8, true)));
+ DictionaryProvider dictionaries = new DictionaryProvider.MapDictionaryProvider(dictionary);
+
+ List fields =
+ Arrays.asList(
+ new Field(
+ "enumField",
+ new FieldType(
+ true,
+ new ArrowType.Int(8, true),
+ new DictionaryEncoding(0L, false, new ArrowType.Int(8, true))),
+ null));
+
+ doRoundTripTest(fields, dictionaries);
+ }
}
diff --git a/adapter/jdbc/pom.xml b/adapter/jdbc/pom.xml
index 2f621d7a05..a8ac19721d 100644
--- a/adapter/jdbc/pom.xml
+++ b/adapter/jdbc/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.arrowarrow-java-root
- 19.0.0-SNAPSHOT
+ 20.0.0-SNAPSHOT../../pom.xml
@@ -59,7 +59,7 @@ under the License.
com.h2databaseh2
- 2.3.232
+ 2.4.240test
diff --git a/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/consumer/CompositeJdbcConsumer.java b/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/consumer/CompositeJdbcConsumer.java
index 2366116fd0..b8389ee27c 100644
--- a/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/consumer/CompositeJdbcConsumer.java
+++ b/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/consumer/CompositeJdbcConsumer.java
@@ -24,7 +24,6 @@
import org.apache.arrow.util.AutoCloseables;
import org.apache.arrow.vector.ValueVector;
import org.apache.arrow.vector.VectorSchemaRoot;
-import org.apache.arrow.vector.types.pojo.ArrowType;
/** Composite consumer which hold all consumers. It manages the consume and cleanup process. */
public class CompositeJdbcConsumer implements JdbcConsumer {
@@ -46,9 +45,9 @@ public void consume(ResultSet rs) throws SQLException, IOException {
BaseConsumer consumer = (BaseConsumer) consumers[i];
JdbcFieldInfo fieldInfo =
new JdbcFieldInfo(rs.getMetaData(), consumer.columnIndexInResultSet);
- ArrowType arrowType = consumer.vector.getMinorType().getType();
+
throw new JdbcConsumerException(
- "Exception while consuming JDBC value", e, fieldInfo, arrowType);
+ "Exception while consuming JDBC value", e, fieldInfo, consumer.vector.getField());
} else {
throw e;
}
diff --git a/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/consumer/exceptions/JdbcConsumerException.java b/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/consumer/exceptions/JdbcConsumerException.java
index 04e26d640c..98927f416c 100644
--- a/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/consumer/exceptions/JdbcConsumerException.java
+++ b/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/consumer/exceptions/JdbcConsumerException.java
@@ -17,7 +17,7 @@
package org.apache.arrow.adapter.jdbc.consumer.exceptions;
import org.apache.arrow.adapter.jdbc.JdbcFieldInfo;
-import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.Field;
/**
* Exception while consuming JDBC data. This exception stores the JdbcFieldInfo for the column and
@@ -25,7 +25,7 @@
*/
public class JdbcConsumerException extends RuntimeException {
final JdbcFieldInfo fieldInfo;
- final ArrowType arrowType;
+ final Field field;
/**
* Construct JdbcConsumerException with all fields.
@@ -33,17 +33,17 @@ public class JdbcConsumerException extends RuntimeException {
* @param message error message
* @param cause original exception
* @param fieldInfo JdbcFieldInfo for the column
- * @param arrowType ArrowType for the corresponding vector
+ * @param field ArrowType for the corresponding vector
*/
public JdbcConsumerException(
- String message, Throwable cause, JdbcFieldInfo fieldInfo, ArrowType arrowType) {
+ String message, Throwable cause, JdbcFieldInfo fieldInfo, Field field) {
super(message, cause);
this.fieldInfo = fieldInfo;
- this.arrowType = arrowType;
+ this.field = field;
}
- public ArrowType getArrowType() {
- return this.arrowType;
+ public Field getField() {
+ return this.field;
}
public JdbcFieldInfo getFieldInfo() {
diff --git a/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/ResultSetUtilityTest.java b/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/ResultSetUtilityTest.java
index c7dc9b2791..e5039ccf59 100644
--- a/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/ResultSetUtilityTest.java
+++ b/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/ResultSetUtilityTest.java
@@ -43,15 +43,19 @@ public void testZeroRowResultSet() throws Exception {
.setReuseVectorSchemaRoot(reuseVectorSchemaRoot)
.build();
- ArrowVectorIterator iter = JdbcToArrow.sqlToArrowVectorIterator(rs, config);
- assertTrue(iter.hasNext(), "Iterator on zero row ResultSet should haveNext() before use");
- VectorSchemaRoot root = iter.next();
- assertNotNull(root, "VectorSchemaRoot from first next() result should never be null");
- assertEquals(
- 0, root.getRowCount(), "VectorSchemaRoot from empty ResultSet should have zero rows");
- assertFalse(
- iter.hasNext(),
- "hasNext() should return false on empty ResultSets after initial next() call");
+ try (ArrowVectorIterator iter = JdbcToArrow.sqlToArrowVectorIterator(rs, config)) {
+ assertTrue(iter.hasNext(), "Iterator on zero row ResultSet should haveNext() before use");
+ VectorSchemaRoot root = iter.next();
+ assertNotNull(root, "VectorSchemaRoot from first next() result should never be null");
+ assertEquals(
+ 0, root.getRowCount(), "VectorSchemaRoot from empty ResultSet should have zero rows");
+ assertFalse(
+ iter.hasNext(),
+ "hasNext() should return false on empty ResultSets after initial next() call");
+ if (!reuseVectorSchemaRoot) {
+ root.close();
+ }
+ }
}
}
}
diff --git a/adapter/orc/pom.xml b/adapter/orc/pom.xml
index e3ae7d5163..50a9b3a603 100644
--- a/adapter/orc/pom.xml
+++ b/adapter/orc/pom.xml
@@ -23,7 +23,7 @@ under the License.
org.apache.arrowarrow-java-root
- 19.0.0-SNAPSHOT
+ 20.0.0-SNAPSHOT../../pom.xml
@@ -61,7 +61,7 @@ under the License.
org.apache.orcorc-core
- 2.1.1
+ 2.3.0test
diff --git a/algorithm/pom.xml b/algorithm/pom.xml
index 898c2605b6..24adcefa6f 100644
--- a/algorithm/pom.xml
+++ b/algorithm/pom.xml
@@ -22,7 +22,7 @@ under the License.
org.apache.arrowarrow-java-root
- 19.0.0-SNAPSHOT
+ 20.0.0-SNAPSHOTarrow-algorithmArrow Algorithms
diff --git a/arrow-format/FlightSql.proto b/arrow-format/FlightSql.proto
index 3568d851cb..b1dc57b33b 100644
--- a/arrow-format/FlightSql.proto
+++ b/arrow-format/FlightSql.proto
@@ -1212,6 +1212,7 @@ message CommandGetDbSchemas {
* - ARROW:FLIGHT:SQL:IS_CASE_SENSITIVE - "1" indicates if the column is case-sensitive, "0" otherwise.
* - ARROW:FLIGHT:SQL:IS_READ_ONLY - "1" indicates if the column is read only, "0" otherwise.
* - ARROW:FLIGHT:SQL:IS_SEARCHABLE - "1" indicates if the column is searchable via WHERE clause, "0" otherwise.
+ * - ARROW:FLIGHT:SQL:REMARKS - A comment describing the column.
* The returned data should be ordered by catalog_name, db_schema_name, table_name, then table_type, followed by table_schema if requested.
*/
message CommandGetTables {
@@ -1549,6 +1550,11 @@ message ActionCreatePreparedStatementResult {
// If the query provided contained parameters, parameter_schema contains the
// schema of the expected parameters. It should be an IPC-encapsulated Schema, as described in Schema.fbs.
bytes parameter_schema = 3;
+
+ // When set to true, the query should be executed with CommandPreparedStatementUpdate,
+ // when set to false, the query should be executed with CommandPreparedStatementQuery.
+ // If not set, the client can choose how to execute the query.
+ optional bool is_update = 4;
}
/*
@@ -1678,6 +1684,7 @@ message ActionEndSavepointRequest {
* - ARROW:FLIGHT:SQL:IS_CASE_SENSITIVE - "1" indicates if the column is case-sensitive, "0" otherwise.
* - ARROW:FLIGHT:SQL:IS_READ_ONLY - "1" indicates if the column is read only, "0" otherwise.
* - ARROW:FLIGHT:SQL:IS_SEARCHABLE - "1" indicates if the column is searchable via WHERE clause, "0" otherwise.
+ * - ARROW:FLIGHT:SQL:REMARKS - A comment describing the column.
* - GetFlightInfo: execute the query.
*/
message CommandStatementQuery {
@@ -1703,6 +1710,7 @@ message CommandStatementQuery {
* - ARROW:FLIGHT:SQL:IS_CASE_SENSITIVE - "1" indicates if the column is case-sensitive, "0" otherwise.
* - ARROW:FLIGHT:SQL:IS_READ_ONLY - "1" indicates if the column is read only, "0" otherwise.
* - ARROW:FLIGHT:SQL:IS_SEARCHABLE - "1" indicates if the column is searchable via WHERE clause, "0" otherwise.
+ * - ARROW:FLIGHT:SQL:REMARKS - A comment describing the column.
* - GetFlightInfo: execute the query.
* - DoPut: execute the query.
*/
@@ -1739,6 +1747,7 @@ message TicketStatementQuery {
* - ARROW:FLIGHT:SQL:IS_CASE_SENSITIVE - "1" indicates if the column is case-sensitive, "0" otherwise.
* - ARROW:FLIGHT:SQL:IS_READ_ONLY - "1" indicates if the column is read only, "0" otherwise.
* - ARROW:FLIGHT:SQL:IS_SEARCHABLE - "1" indicates if the column is searchable via WHERE clause, "0" otherwise.
+ * - ARROW:FLIGHT:SQL:REMARKS - A comment describing the column.
*
* If the schema is retrieved after parameter values have been bound with DoPut, then the server should account
* for the parameters when determining the schema.
diff --git a/arrow-variant/pom.xml b/arrow-variant/pom.xml
new file mode 100644
index 0000000000..e578626dd4
--- /dev/null
+++ b/arrow-variant/pom.xml
@@ -0,0 +1,51 @@
+
+
+
+ 4.0.0
+
+ org.apache.arrow
+ arrow-java-root
+ 20.0.0-SNAPSHOT
+
+ arrow-variant
+ Arrow Variant
+ Arrow Variant type support.
+
+
+
+ org.apache.arrow
+ arrow-memory-core
+
+
+ org.apache.arrow
+ arrow-vector
+
+
+ org.apache.parquet
+ parquet-variant
+ ${dep.parquet.version}
+
+
+ org.apache.arrow
+ arrow-memory-unsafe
+ test
+
+
+
diff --git a/vector/src/test/java/org/apache/arrow/vector/holder/UuidHolder.java b/arrow-variant/src/main/java/module-info.java
similarity index 69%
rename from vector/src/test/java/org/apache/arrow/vector/holder/UuidHolder.java
rename to arrow-variant/src/main/java/module-info.java
index 207b0951a7..da94173969 100644
--- a/vector/src/test/java/org/apache/arrow/vector/holder/UuidHolder.java
+++ b/arrow-variant/src/main/java/module-info.java
@@ -14,10 +14,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.arrow.vector.holder;
-import org.apache.arrow.vector.holders.ExtensionHolder;
+@SuppressWarnings("requires-automatic")
+module org.apache.arrow.variant {
+ exports org.apache.arrow.variant;
+ exports org.apache.arrow.variant.extension;
+ exports org.apache.arrow.variant.impl;
+ exports org.apache.arrow.variant.holders;
-public class UuidHolder extends ExtensionHolder {
- public byte[] value;
+ requires org.apache.arrow.memory.core;
+ requires org.apache.arrow.vector;
+ requires parquet.variant;
}
diff --git a/arrow-variant/src/main/java/org/apache/arrow/variant/Variant.java b/arrow-variant/src/main/java/org/apache/arrow/variant/Variant.java
new file mode 100644
index 0000000000..fa05cdd93f
--- /dev/null
+++ b/arrow-variant/src/main/java/org/apache/arrow/variant/Variant.java
@@ -0,0 +1,217 @@
+/*
+ * 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.variant;
+
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.UUID;
+import org.apache.arrow.memory.ArrowBuf;
+import org.apache.arrow.variant.holders.NullableVariantHolder;
+
+/**
+ * Wrapper around parquet-variant's Variant implementation.
+ *
+ *
This wrapper exists to isolate the parquet-variant dependency from Arrow's public API,
+ * allowing the vector module to expose variant functionality without requiring users to depend on
+ * parquet-variant directly. It also ensures that nested variant values (from arrays and objects)
+ * are consistently wrapped.
+ */
+public class Variant {
+
+ private final org.apache.parquet.variant.Variant delegate;
+
+ /** Creates a Variant from raw metadata and value byte arrays. */
+ public Variant(byte[] metadata, byte[] value) {
+ this.delegate = new org.apache.parquet.variant.Variant(value, metadata);
+ }
+
+ /** Creates a Variant by copying data from ArrowBuf instances. */
+ public Variant(
+ ArrowBuf metadataBuffer,
+ int metadataStart,
+ int metadataEnd,
+ ArrowBuf valueBuffer,
+ int valueStart,
+ int valueEnd) {
+ byte[] metadata = new byte[metadataEnd - metadataStart];
+ byte[] value = new byte[valueEnd - valueStart];
+ metadataBuffer.getBytes(metadataStart, metadata);
+ valueBuffer.getBytes(valueStart, value);
+ this.delegate = new org.apache.parquet.variant.Variant(value, metadata);
+ }
+
+ private Variant(org.apache.parquet.variant.Variant delegate) {
+ this.delegate = delegate;
+ }
+
+ /** Constructs a Variant from a NullableVariantHolder. */
+ public Variant(NullableVariantHolder holder) {
+ this(
+ holder.metadataBuffer,
+ holder.metadataStart,
+ holder.metadataEnd,
+ holder.valueBuffer,
+ holder.valueStart,
+ holder.valueEnd);
+ }
+
+ public ByteBuffer getValueBuffer() {
+ return delegate.getValueBuffer();
+ }
+
+ public ByteBuffer getMetadataBuffer() {
+ return delegate.getMetadataBuffer();
+ }
+
+ public boolean getBoolean() {
+ return delegate.getBoolean();
+ }
+
+ public byte getByte() {
+ return delegate.getByte();
+ }
+
+ public short getShort() {
+ return delegate.getShort();
+ }
+
+ public int getInt() {
+ return delegate.getInt();
+ }
+
+ public long getLong() {
+ return delegate.getLong();
+ }
+
+ public double getDouble() {
+ return delegate.getDouble();
+ }
+
+ public BigDecimal getDecimal() {
+ return delegate.getDecimal();
+ }
+
+ public float getFloat() {
+ return delegate.getFloat();
+ }
+
+ public ByteBuffer getBinary() {
+ return delegate.getBinary();
+ }
+
+ public UUID getUUID() {
+ return delegate.getUUID();
+ }
+
+ public String getString() {
+ return delegate.getString();
+ }
+
+ public Type getType() {
+ return Type.fromParquet(delegate.getType());
+ }
+
+ public int numObjectElements() {
+ return delegate.numObjectElements();
+ }
+
+ public Variant getFieldByKey(String key) {
+ org.apache.parquet.variant.Variant result = delegate.getFieldByKey(key);
+ return result != null ? wrap(result) : null;
+ }
+
+ public ObjectField getFieldAtIndex(int idx) {
+ org.apache.parquet.variant.Variant.ObjectField field = delegate.getFieldAtIndex(idx);
+ return new ObjectField(field.key, wrap(field.value));
+ }
+
+ public int numArrayElements() {
+ return delegate.numArrayElements();
+ }
+
+ public Variant getElementAtIndex(int index) {
+ org.apache.parquet.variant.Variant result = delegate.getElementAtIndex(index);
+ return result != null ? wrap(result) : null;
+ }
+
+ private static Variant wrap(org.apache.parquet.variant.Variant parquetVariant) {
+ return new Variant(parquetVariant);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Variant variant = (Variant) o;
+ return delegate.getMetadataBuffer().equals(variant.delegate.getMetadataBuffer())
+ && delegate.getValueBuffer().equals(variant.delegate.getValueBuffer());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(delegate.getMetadataBuffer(), delegate.getValueBuffer());
+ }
+
+ @Override
+ public String toString() {
+ return "Variant{type=" + getType() + '}';
+ }
+
+ public enum Type {
+ OBJECT,
+ ARRAY,
+ NULL,
+ BOOLEAN,
+ BYTE,
+ SHORT,
+ INT,
+ LONG,
+ STRING,
+ DOUBLE,
+ DECIMAL4,
+ DECIMAL8,
+ DECIMAL16,
+ DATE,
+ TIMESTAMP_TZ,
+ TIMESTAMP_NTZ,
+ FLOAT,
+ BINARY,
+ TIME,
+ TIMESTAMP_NANOS_TZ,
+ TIMESTAMP_NANOS_NTZ,
+ UUID;
+
+ static Type fromParquet(org.apache.parquet.variant.Variant.Type parquetType) {
+ return Type.valueOf(parquetType.name());
+ }
+ }
+
+ public static final class ObjectField {
+ public final String key;
+ public final Variant value;
+
+ public ObjectField(String key, Variant value) {
+ this.key = key;
+ this.value = value;
+ }
+ }
+}
diff --git a/arrow-variant/src/main/java/org/apache/arrow/variant/extension/VariantType.java b/arrow-variant/src/main/java/org/apache/arrow/variant/extension/VariantType.java
new file mode 100644
index 0000000000..3deb70cdc0
--- /dev/null
+++ b/arrow-variant/src/main/java/org/apache/arrow/variant/extension/VariantType.java
@@ -0,0 +1,93 @@
+/*
+ * 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.variant.extension;
+
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.variant.impl.VariantWriterImpl;
+import org.apache.arrow.vector.FieldVector;
+import org.apache.arrow.vector.ValueVector;
+import org.apache.arrow.vector.complex.writer.FieldWriter;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.ArrowType.ExtensionType;
+import org.apache.arrow.vector.types.pojo.ExtensionTypeRegistry;
+import org.apache.arrow.vector.types.pojo.FieldType;
+
+/**
+ * Arrow extension type for Parquet
+ * Variant binary encoding. The type itself does not support shredded variant data.
+ */
+public final class VariantType extends ExtensionType {
+
+ public static final VariantType INSTANCE = new VariantType();
+
+ public static final String EXTENSION_NAME = "parquet.variant";
+
+ static {
+ ExtensionTypeRegistry.register(INSTANCE);
+ }
+
+ private VariantType() {}
+
+ @Override
+ public ArrowType storageType() {
+ return ArrowType.Struct.INSTANCE;
+ }
+
+ @Override
+ public String extensionName() {
+ return EXTENSION_NAME;
+ }
+
+ @Override
+ public boolean extensionEquals(ExtensionType other) {
+ return other instanceof VariantType;
+ }
+
+ @Override
+ public String serialize() {
+ return "";
+ }
+
+ @Override
+ public ArrowType deserialize(ArrowType storageType, String serializedData) {
+ if (!storageType.equals(this.storageType())) {
+ throw new UnsupportedOperationException(
+ "Cannot construct VariantType from underlying type " + storageType);
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public FieldVector getNewVector(String name, FieldType fieldType, BufferAllocator allocator) {
+ return new VariantVector(name, allocator);
+ }
+
+ @Override
+ public boolean isComplex() {
+ // The type itself is not complex meaning we need separate functions to convert/extract
+ // different types.
+ // Meanwhile, the containing vector is complex in terms of containing multiple values (metadata
+ // and value)
+ return false;
+ }
+
+ @Override
+ public FieldWriter getNewFieldWriter(ValueVector vector) {
+ return new VariantWriterImpl((VariantVector) vector);
+ }
+}
diff --git a/arrow-variant/src/main/java/org/apache/arrow/variant/extension/VariantVector.java b/arrow-variant/src/main/java/org/apache/arrow/variant/extension/VariantVector.java
new file mode 100644
index 0000000000..1bbf1a6bdb
--- /dev/null
+++ b/arrow-variant/src/main/java/org/apache/arrow/variant/extension/VariantVector.java
@@ -0,0 +1,348 @@
+/*
+ * 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.variant.extension;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.arrow.memory.ArrowBuf;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.util.hash.ArrowBufHasher;
+import org.apache.arrow.variant.Variant;
+import org.apache.arrow.variant.holders.NullableVariantHolder;
+import org.apache.arrow.variant.holders.VariantHolder;
+import org.apache.arrow.vector.BitVectorHelper;
+import org.apache.arrow.vector.ExtensionTypeVector;
+import org.apache.arrow.vector.FieldVector;
+import org.apache.arrow.vector.ValueVector;
+import org.apache.arrow.vector.VarBinaryVector;
+import org.apache.arrow.vector.complex.AbstractStructVector;
+import org.apache.arrow.vector.complex.StructVector;
+import org.apache.arrow.vector.complex.reader.FieldReader;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.ArrowType.Binary;
+import org.apache.arrow.vector.types.pojo.Field;
+import org.apache.arrow.vector.types.pojo.FieldType;
+import org.apache.arrow.vector.util.CallBack;
+import org.apache.arrow.vector.util.TransferPair;
+
+/**
+ * Arrow vector for storing {@link VariantType} values.
+ *
+ *
Stores semi-structured data (like JSON) as metadata + value binary pairs, allowing
+ * type-flexible columnar storage within Arrow's type system.
+ */
+public class VariantVector extends ExtensionTypeVector {
+
+ public static final String METADATA_VECTOR_NAME = "metadata";
+ public static final String VALUE_VECTOR_NAME = "value";
+
+ private final Field rootField;
+
+ /**
+ * Constructs a new VariantVector with the given name and allocator.
+ *
+ * @param name the name of the vector
+ * @param allocator the buffer allocator for memory management
+ */
+ public VariantVector(String name, BufferAllocator allocator) {
+ super(
+ name,
+ allocator,
+ new StructVector(
+ name,
+ allocator,
+ FieldType.nullable(ArrowType.Struct.INSTANCE),
+ null,
+ AbstractStructVector.ConflictPolicy.CONFLICT_ERROR,
+ false));
+ rootField = createVariantField(name);
+ ((FieldVector) this.getUnderlyingVector())
+ .initializeChildrenFromFields(rootField.getChildren());
+ }
+
+ /**
+ * Creates a new VariantVector with the given name. The Variant Field schema has to be the same
+ * everywhere, otherwise ArrowBuffer loading might fail during serialization/deserialization and
+ * schema mismatches can occur. This includes CompleteType's VARIANT and VARIANT_REQUIRED types.
+ */
+ public static Field createVariantField(String name) {
+ return new Field(
+ name, new FieldType(true, VariantType.INSTANCE, null), createVariantChildFields());
+ }
+
+ /**
+ * Creates the child fields for the VariantVector. Metadata vector will be index 0 and value
+ * vector will be index 1.
+ */
+ public static List createVariantChildFields() {
+ return List.of(
+ new Field(METADATA_VECTOR_NAME, new FieldType(false, Binary.INSTANCE, null), null),
+ new Field(VALUE_VECTOR_NAME, new FieldType(false, Binary.INSTANCE, null), null));
+ }
+
+ @Override
+ public void initializeChildrenFromFields(List children) {
+ // No-op, as children are initialized in the constructor
+ }
+
+ @Override
+ public Field getField() {
+ return rootField;
+ }
+
+ public VarBinaryVector getMetadataVector() {
+ return getUnderlyingVector().getChild(METADATA_VECTOR_NAME, VarBinaryVector.class);
+ }
+
+ public VarBinaryVector getValueVector() {
+ return getUnderlyingVector().getChild(VALUE_VECTOR_NAME, VarBinaryVector.class);
+ }
+
+ @Override
+ public TransferPair makeTransferPair(ValueVector target) {
+ return new VariantTransferPair(this, (VariantVector) target);
+ }
+
+ @Override
+ public TransferPair getTransferPair(Field field, BufferAllocator allocator) {
+ return new VariantTransferPair(this, new VariantVector(field.getName(), allocator));
+ }
+
+ @Override
+ public TransferPair getTransferPair(Field field, BufferAllocator allocator, CallBack callBack) {
+ return getTransferPair(field, allocator);
+ }
+
+ @Override
+ public TransferPair getTransferPair(String ref, BufferAllocator allocator) {
+ return new VariantTransferPair(this, new VariantVector(ref, allocator));
+ }
+
+ @Override
+ public TransferPair getTransferPair(String ref, BufferAllocator allocator, CallBack callBack) {
+ return getTransferPair(ref, allocator);
+ }
+
+ @Override
+ public TransferPair getTransferPair(BufferAllocator allocator) {
+ return getTransferPair(this.getField().getName(), allocator);
+ }
+
+ @Override
+ public void copyFrom(int fromIndex, int thisIndex, ValueVector from) {
+ getUnderlyingVector()
+ .copyFrom(fromIndex, thisIndex, ((VariantVector) from).getUnderlyingVector());
+ }
+
+ @Override
+ public void copyFromSafe(int fromIndex, int thisIndex, ValueVector from) {
+ getUnderlyingVector()
+ .copyFromSafe(fromIndex, thisIndex, ((VariantVector) from).getUnderlyingVector());
+ }
+
+ @Override
+ public Object getObject(int index) {
+ if (isNull(index)) {
+ return null;
+ }
+ VarBinaryVector metadataVector = getMetadataVector();
+ VarBinaryVector valueVector = getValueVector();
+
+ int metadataStart = metadataVector.getStartOffset(index);
+ int metadataEnd = metadataVector.getEndOffset(index);
+ int valueStart = valueVector.getStartOffset(index);
+ int valueEnd = valueVector.getEndOffset(index);
+
+ return new Variant(
+ metadataVector.getDataBuffer(),
+ metadataStart,
+ metadataEnd,
+ valueVector.getDataBuffer(),
+ valueStart,
+ valueEnd);
+ }
+
+ /**
+ * Retrieves the variant value at the specified index into the provided holder.
+ *
+ * @param index the index of the value to retrieve
+ * @param holder the holder to populate with the variant data
+ */
+ public void get(int index, NullableVariantHolder holder) {
+ if (isNull(index)) {
+ holder.isSet = 0;
+ } else {
+ holder.isSet = 1;
+ VarBinaryVector metadataVector = getMetadataVector();
+ VarBinaryVector valueVector = getValueVector();
+ assert !metadataVector.isNull(index) && !valueVector.isNull(index);
+
+ holder.metadataStart = metadataVector.getStartOffset(index);
+ holder.metadataEnd = metadataVector.getEndOffset(index);
+ holder.metadataBuffer = metadataVector.getDataBuffer();
+ holder.valueStart = valueVector.getStartOffset(index);
+ holder.valueEnd = valueVector.getEndOffset(index);
+ holder.valueBuffer = valueVector.getDataBuffer();
+ }
+ }
+
+ /**
+ * Retrieves the variant value at the specified index into the provided non-nullable holder.
+ *
+ * @param index the index of the value to retrieve
+ * @param holder the holder to populate with the variant data
+ */
+ public void get(int index, VariantHolder holder) {
+ VarBinaryVector metadataVector = getMetadataVector();
+ VarBinaryVector valueVector = getValueVector();
+ assert !metadataVector.isNull(index) && !valueVector.isNull(index);
+
+ holder.metadataStart = metadataVector.getStartOffset(index);
+ holder.metadataEnd = metadataVector.getEndOffset(index);
+ holder.metadataBuffer = metadataVector.getDataBuffer();
+ holder.valueStart = valueVector.getStartOffset(index);
+ holder.valueEnd = valueVector.getEndOffset(index);
+ holder.valueBuffer = valueVector.getDataBuffer();
+ }
+
+ /**
+ * Sets the variant value at the specified index from the provided holder.
+ *
+ * @param index the index at which to set the value
+ * @param holder the holder containing the variant data to set
+ */
+ public void set(int index, VariantHolder holder) {
+ BitVectorHelper.setBit(getUnderlyingVector().getValidityBuffer(), index);
+ getMetadataVector()
+ .set(index, 1, holder.metadataStart, holder.metadataEnd, holder.metadataBuffer);
+ getValueVector().set(index, 1, holder.valueStart, holder.valueEnd, holder.valueBuffer);
+ }
+
+ /**
+ * Sets the variant value at the specified index from the provided nullable holder.
+ *
+ * @param index the index at which to set the value
+ * @param holder the nullable holder containing the variant data to set
+ */
+ public void set(int index, NullableVariantHolder holder) {
+ BitVectorHelper.setValidityBit(getUnderlyingVector().getValidityBuffer(), index, holder.isSet);
+ if (holder.isSet == 0) {
+ return;
+ }
+ getMetadataVector()
+ .set(index, 1, holder.metadataStart, holder.metadataEnd, holder.metadataBuffer);
+ getValueVector().set(index, 1, holder.valueStart, holder.valueEnd, holder.valueBuffer);
+ }
+
+ /**
+ * Sets the variant value at the specified index from the provided holder, with bounds checking.
+ *
+ * @param index the index at which to set the value
+ * @param holder the holder containing the variant data to set
+ */
+ public void setSafe(int index, VariantHolder holder) {
+ getUnderlyingVector().setIndexDefined(index);
+ getMetadataVector()
+ .setSafe(index, 1, holder.metadataStart, holder.metadataEnd, holder.metadataBuffer);
+ getValueVector().setSafe(index, 1, holder.valueStart, holder.valueEnd, holder.valueBuffer);
+ }
+
+ /**
+ * Sets the variant value at the specified index from the provided nullable holder, with bounds
+ * checking.
+ *
+ * @param index the index at which to set the value
+ * @param holder the nullable holder containing the variant data to set
+ */
+ public void setSafe(int index, NullableVariantHolder holder) {
+ if (holder.isSet == 0) {
+ getUnderlyingVector().setNull(index);
+ return;
+ }
+ getUnderlyingVector().setIndexDefined(index);
+ getMetadataVector()
+ .setSafe(index, 1, holder.metadataStart, holder.metadataEnd, holder.metadataBuffer);
+ getValueVector().setSafe(index, 1, holder.valueStart, holder.valueEnd, holder.valueBuffer);
+ }
+
+ /** Sets the value at the given index from the provided Variant. */
+ public void setSafe(int index, Variant variant) {
+ ByteBuffer metadataBuffer = variant.getMetadataBuffer();
+ ByteBuffer valueBuffer = variant.getValueBuffer();
+ int metadataLength = metadataBuffer.remaining();
+ int valueLength = valueBuffer.remaining();
+ try (ArrowBuf metaBuf = getAllocator().buffer(metadataLength);
+ ArrowBuf valBuf = getAllocator().buffer(valueLength)) {
+ metaBuf.setBytes(0, metadataBuffer.duplicate());
+ valBuf.setBytes(0, valueBuffer.duplicate());
+ getUnderlyingVector().setIndexDefined(index);
+ getMetadataVector().setSafe(index, 1, 0, metadataLength, metaBuf);
+ getValueVector().setSafe(index, 1, 0, valueLength, valBuf);
+ }
+ }
+
+ @Override
+ protected FieldReader getReaderImpl() {
+ return new org.apache.arrow.variant.impl.VariantReaderImpl(this);
+ }
+
+ @Override
+ public int hashCode(int index) {
+ return hashCode(index, null);
+ }
+
+ @Override
+ public int hashCode(int index, ArrowBufHasher hasher) {
+ return getUnderlyingVector().hashCode(index, hasher);
+ }
+
+ /**
+ * VariantTransferPair is a transfer pair for VariantVector. It transfers the metadata and value
+ * together using the underlyingVector's transfer pair.
+ */
+ protected static class VariantTransferPair implements TransferPair {
+ private final TransferPair pair;
+ private final VariantVector from;
+ private final VariantVector to;
+
+ public VariantTransferPair(VariantVector from, VariantVector to) {
+ this.from = from;
+ this.to = to;
+ this.pair = from.getUnderlyingVector().makeTransferPair((to).getUnderlyingVector());
+ }
+
+ @Override
+ public void transfer() {
+ pair.transfer();
+ }
+
+ @Override
+ public void splitAndTransfer(int startIndex, int length) {
+ pair.splitAndTransfer(startIndex, length);
+ }
+
+ @Override
+ public ValueVector getTo() {
+ return to;
+ }
+
+ @Override
+ public void copyValueSafe(int from, int to) {
+ pair.copyValueSafe(from, to);
+ }
+ }
+}
diff --git a/arrow-variant/src/main/java/org/apache/arrow/variant/holders/NullableVariantHolder.java b/arrow-variant/src/main/java/org/apache/arrow/variant/holders/NullableVariantHolder.java
new file mode 100644
index 0000000000..b78d4a2013
--- /dev/null
+++ b/arrow-variant/src/main/java/org/apache/arrow/variant/holders/NullableVariantHolder.java
@@ -0,0 +1,56 @@
+/*
+ * 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.variant.holders;
+
+import org.apache.arrow.memory.ArrowBuf;
+import org.apache.arrow.variant.extension.VariantType;
+import org.apache.arrow.vector.holders.ExtensionHolder;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+
+@SuppressWarnings("checkstyle:VisibilityModifier")
+public final class NullableVariantHolder extends ExtensionHolder {
+
+ public int isSet;
+ public int metadataStart;
+ public int metadataEnd;
+ public ArrowBuf metadataBuffer;
+ public int valueStart;
+ public int valueEnd;
+ public ArrowBuf valueBuffer;
+
+ public NullableVariantHolder() {}
+
+ @Override
+ public boolean equals(Object obj) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int hashCode() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String toString() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ArrowType type() {
+ return VariantType.INSTANCE;
+ }
+}
diff --git a/vector/src/test/java/org/apache/arrow/vector/complex/impl/UuidWriterImpl.java b/arrow-variant/src/main/java/org/apache/arrow/variant/holders/VariantHolder.java
similarity index 51%
rename from vector/src/test/java/org/apache/arrow/vector/complex/impl/UuidWriterImpl.java
rename to arrow-variant/src/main/java/org/apache/arrow/variant/holders/VariantHolder.java
index 68029b1df5..e3947ac439 100644
--- a/vector/src/test/java/org/apache/arrow/vector/complex/impl/UuidWriterImpl.java
+++ b/arrow-variant/src/main/java/org/apache/arrow/variant/holders/VariantHolder.java
@@ -14,34 +14,43 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.arrow.vector.complex.impl;
+package org.apache.arrow.variant.holders;
-import java.nio.ByteBuffer;
-import java.util.UUID;
-import org.apache.arrow.vector.UuidVector;
-import org.apache.arrow.vector.holder.UuidHolder;
+import org.apache.arrow.memory.ArrowBuf;
+import org.apache.arrow.variant.extension.VariantType;
import org.apache.arrow.vector.holders.ExtensionHolder;
+import org.apache.arrow.vector.types.pojo.ArrowType;
-public class UuidWriterImpl extends AbstractExtensionTypeWriter {
+@SuppressWarnings("checkstyle:VisibilityModifier")
+public final class VariantHolder extends ExtensionHolder {
- public UuidWriterImpl(UuidVector vector) {
- super(vector);
+ public final int isSet = 1;
+ public int metadataStart;
+ public int metadataEnd;
+ public ArrowBuf metadataBuffer;
+ public int valueStart;
+ public int valueEnd;
+ public ArrowBuf valueBuffer;
+
+ public VariantHolder() {}
+
+ @Override
+ public boolean equals(Object obj) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int hashCode() {
+ throw new UnsupportedOperationException();
}
@Override
- public void writeExtension(Object value) {
- UUID uuid = (UUID) value;
- ByteBuffer bb = ByteBuffer.allocate(16);
- bb.putLong(uuid.getMostSignificantBits());
- bb.putLong(uuid.getLeastSignificantBits());
- vector.setSafe(getPosition(), bb.array());
- vector.setValueCount(getPosition() + 1);
+ public String toString() {
+ throw new UnsupportedOperationException();
}
@Override
- public void write(ExtensionHolder holder) {
- UuidHolder uuidHolder = (UuidHolder) holder;
- vector.setSafe(getPosition(), uuidHolder.value);
- vector.setValueCount(getPosition() + 1);
+ public ArrowType type() {
+ return VariantType.INSTANCE;
}
}
diff --git a/arrow-variant/src/main/java/org/apache/arrow/variant/impl/NullableVariantHolderReaderImpl.java b/arrow-variant/src/main/java/org/apache/arrow/variant/impl/NullableVariantHolderReaderImpl.java
new file mode 100644
index 0000000000..1645529c0c
--- /dev/null
+++ b/arrow-variant/src/main/java/org/apache/arrow/variant/impl/NullableVariantHolderReaderImpl.java
@@ -0,0 +1,69 @@
+/*
+ * 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.variant.impl;
+
+import org.apache.arrow.variant.holders.NullableVariantHolder;
+import org.apache.arrow.vector.complex.impl.AbstractFieldReader;
+import org.apache.arrow.vector.types.Types;
+
+public class NullableVariantHolderReaderImpl extends AbstractFieldReader {
+ private final NullableVariantHolder holder;
+
+ public NullableVariantHolderReaderImpl(NullableVariantHolder holder) {
+ this.holder = holder;
+ }
+
+ @Override
+ public int size() {
+ throw new UnsupportedOperationException("You can't call size on a Holder value reader.");
+ }
+
+ @Override
+ public boolean next() {
+ throw new UnsupportedOperationException("You can't call next on a single value reader.");
+ }
+
+ @Override
+ public void setPosition(int index) {
+ throw new UnsupportedOperationException("You can't call setPosition on a single value reader.");
+ }
+
+ @Override
+ public Types.MinorType getMinorType() {
+ return Types.MinorType.EXTENSIONTYPE;
+ }
+
+ @Override
+ public boolean isSet() {
+ return holder.isSet == 1;
+ }
+
+ /**
+ * Reads the variant holder data into the provided holder.
+ *
+ * @param h the holder to read into
+ */
+ public void read(NullableVariantHolder h) {
+ h.metadataStart = this.holder.metadataStart;
+ h.metadataEnd = this.holder.metadataEnd;
+ h.metadataBuffer = this.holder.metadataBuffer;
+ h.valueStart = this.holder.valueStart;
+ h.valueEnd = this.holder.valueEnd;
+ h.valueBuffer = this.holder.valueBuffer;
+ h.isSet = this.isSet() ? 1 : 0;
+ }
+}
diff --git a/arrow-variant/src/main/java/org/apache/arrow/variant/impl/VariantReaderImpl.java b/arrow-variant/src/main/java/org/apache/arrow/variant/impl/VariantReaderImpl.java
new file mode 100644
index 0000000000..670104b7d1
--- /dev/null
+++ b/arrow-variant/src/main/java/org/apache/arrow/variant/impl/VariantReaderImpl.java
@@ -0,0 +1,73 @@
+/*
+ * 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.variant.impl;
+
+import org.apache.arrow.variant.extension.VariantVector;
+import org.apache.arrow.variant.holders.NullableVariantHolder;
+import org.apache.arrow.variant.holders.VariantHolder;
+import org.apache.arrow.vector.complex.impl.AbstractFieldReader;
+import org.apache.arrow.vector.holders.ExtensionHolder;
+import org.apache.arrow.vector.types.Types;
+import org.apache.arrow.vector.types.pojo.Field;
+
+public class VariantReaderImpl extends AbstractFieldReader {
+ private final VariantVector vector;
+
+ public VariantReaderImpl(VariantVector vector) {
+ this.vector = vector;
+ }
+
+ @Override
+ public Types.MinorType getMinorType() {
+ return this.vector.getMinorType();
+ }
+
+ @Override
+ public Field getField() {
+ return this.vector.getField();
+ }
+
+ @Override
+ public boolean isSet() {
+ return !this.vector.isNull(this.idx());
+ }
+
+ @Override
+ public void read(ExtensionHolder holder) {
+ if (holder instanceof VariantHolder) {
+ vector.get(idx(), (VariantHolder) holder);
+ } else if (holder instanceof NullableVariantHolder) {
+ vector.get(idx(), (NullableVariantHolder) holder);
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported holder type for VariantReader: " + holder.getClass());
+ }
+ }
+
+ public void read(VariantHolder h) {
+ this.vector.get(this.idx(), h);
+ }
+
+ public void read(NullableVariantHolder h) {
+ this.vector.get(this.idx(), h);
+ }
+
+ @Override
+ public Object readObject() {
+ return this.vector.getObject(this.idx());
+ }
+}
diff --git a/arrow-variant/src/main/java/org/apache/arrow/variant/impl/VariantWriterImpl.java b/arrow-variant/src/main/java/org/apache/arrow/variant/impl/VariantWriterImpl.java
new file mode 100644
index 0000000000..266ddb75d2
--- /dev/null
+++ b/arrow-variant/src/main/java/org/apache/arrow/variant/impl/VariantWriterImpl.java
@@ -0,0 +1,121 @@
+/*
+ * 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.variant.impl;
+
+import org.apache.arrow.memory.ArrowBuf;
+import org.apache.arrow.variant.Variant;
+import org.apache.arrow.variant.extension.VariantVector;
+import org.apache.arrow.variant.holders.NullableVariantHolder;
+import org.apache.arrow.variant.holders.VariantHolder;
+import org.apache.arrow.vector.complex.impl.AbstractExtensionTypeWriter;
+import org.apache.arrow.vector.holders.ExtensionHolder;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+
+/**
+ * Writer implementation for VARIANT extension type vectors.
+ *
+ *
This writer handles writing variant data to a {@link VariantVector}. It accepts both {@link
+ * VariantHolder} and {@link NullableVariantHolder} objects containing metadata and value buffers
+ * and writes them to the appropriate position in the vector.
+ */
+public class VariantWriterImpl extends AbstractExtensionTypeWriter {
+
+ private static final String UNSUPPORTED_TYPE_TEMPLATE = "Unsupported type for Variant: %s";
+
+ /**
+ * Constructs a new VariantWriterImpl for the given vector.
+ *
+ * @param vector the variant vector to write to
+ */
+ public VariantWriterImpl(VariantVector vector) {
+ super(vector);
+ }
+
+ /**
+ * Writes an extension type or variant value to the vector.
+ *
+ *
This method handles {@link ExtensionHolder} by delegating to {@link #write(ExtensionHolder)}
+ * and {@link Variant} by delegating to {@link #writeVariant(Variant)}.
+ *
+ * @param object the object to write, must be an {@link ExtensionHolder} or {@link Variant}
+ * @throws IllegalArgumentException if the object is not an {@link ExtensionHolder} or {@link
+ * Variant}
+ */
+ @Override
+ public void writeExtension(Object object) {
+ if (object instanceof ExtensionHolder) {
+ write((ExtensionHolder) object);
+ } else if (object instanceof Variant) {
+ writeVariant((Variant) object);
+ } else {
+ throw new IllegalArgumentException(
+ String.format(UNSUPPORTED_TYPE_TEMPLATE, object.getClass().getName()));
+ }
+ }
+
+ private void writeVariant(Variant variant) {
+ java.nio.ByteBuffer metadataBuffer = variant.getMetadataBuffer();
+ java.nio.ByteBuffer valueBuffer = variant.getValueBuffer();
+ int metadataLength = metadataBuffer.remaining();
+ int valueLength = valueBuffer.remaining();
+ try (ArrowBuf metadataBuf = vector.getAllocator().buffer(metadataLength);
+ ArrowBuf valueBuf = vector.getAllocator().buffer(valueLength)) {
+ metadataBuf.setBytes(0, metadataBuffer.duplicate());
+ valueBuf.setBytes(0, valueBuffer.duplicate());
+ NullableVariantHolder holder = new NullableVariantHolder();
+ holder.isSet = 1;
+ holder.metadataBuffer = metadataBuf;
+ holder.metadataStart = 0;
+ holder.metadataEnd = metadataLength;
+ holder.valueBuffer = valueBuf;
+ holder.valueStart = 0;
+ holder.valueEnd = valueLength;
+ vector.setSafe(getPosition(), holder);
+ vector.setValueCount(getPosition() + 1);
+ }
+ }
+
+ @Override
+ public void writeExtension(Object value, ArrowType type) {
+ writeExtension(value);
+ }
+
+ /**
+ * Writes a variant holder to the vector at the current position.
+ *
+ *
The holder can be either a {@link VariantHolder} (non-nullable, always set) or a {@link
+ * NullableVariantHolder} (nullable, may be null). The data is written using {@link
+ * VariantVector#setSafe(int, NullableVariantHolder)} which handles buffer allocation and copying.
+ *
+ * @param extensionHolder the variant holder to write, must be a {@link VariantHolder} or {@link
+ * NullableVariantHolder}
+ * @throws IllegalArgumentException if the holder is neither a {@link VariantHolder} nor a {@link
+ * NullableVariantHolder}
+ */
+ @Override
+ public void write(ExtensionHolder extensionHolder) {
+ if (extensionHolder instanceof VariantHolder) {
+ vector.setSafe(getPosition(), (VariantHolder) extensionHolder);
+ } else if (extensionHolder instanceof NullableVariantHolder) {
+ vector.setSafe(getPosition(), (NullableVariantHolder) extensionHolder);
+ } else {
+ throw new IllegalArgumentException(
+ String.format(UNSUPPORTED_TYPE_TEMPLATE, extensionHolder.getClass().getName()));
+ }
+ vector.setValueCount(getPosition() + 1);
+ }
+}
diff --git a/arrow-variant/src/test/java/org/apache/arrow/variant/TestVariant.java b/arrow-variant/src/test/java/org/apache/arrow/variant/TestVariant.java
new file mode 100644
index 0000000000..bc46a68616
--- /dev/null
+++ b/arrow-variant/src/test/java/org/apache/arrow/variant/TestVariant.java
@@ -0,0 +1,439 @@
+/*
+ * 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.variant;
+
+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.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import java.util.UUID;
+import org.apache.arrow.memory.ArrowBuf;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.parquet.variant.VariantBuilder;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TestVariant {
+
+ private BufferAllocator allocator;
+
+ @BeforeEach
+ void beforeEach() {
+ allocator = new RootAllocator();
+ }
+
+ @AfterEach
+ void afterEach() {
+ allocator.close();
+ }
+
+ static Variant buildVariant(VariantBuilder builder) {
+ org.apache.parquet.variant.Variant parquetVariant = builder.build();
+ ByteBuffer valueBuf = parquetVariant.getValueBuffer();
+ ByteBuffer metaBuf = parquetVariant.getMetadataBuffer();
+ byte[] valueBytes = new byte[valueBuf.remaining()];
+ byte[] metaBytes = new byte[metaBuf.remaining()];
+ valueBuf.get(valueBytes);
+ metaBuf.get(metaBytes);
+ return new Variant(metaBytes, valueBytes);
+ }
+
+ public static Variant variantString(String value) {
+ VariantBuilder builder = new VariantBuilder();
+ builder.appendString(value);
+ return buildVariant(builder);
+ }
+
+ @Test
+ void testConstructionWithArrowBuf() {
+ VariantBuilder builder = new VariantBuilder();
+ builder.appendInt(42);
+ Variant source = buildVariant(builder);
+ int metaLen = source.getMetadataBuffer().remaining();
+ int valueLen = source.getValueBuffer().remaining();
+
+ try (ArrowBuf metadataArrowBuf = allocator.buffer(metaLen + 2);
+ ArrowBuf valueArrowBuf = allocator.buffer(valueLen + 3)) {
+ metadataArrowBuf.setBytes(2, source.getMetadataBuffer());
+ valueArrowBuf.setBytes(3, source.getValueBuffer());
+
+ Variant variant =
+ new Variant(metadataArrowBuf, 2, 2 + metaLen, valueArrowBuf, 3, 3 + valueLen);
+
+ assertEquals(Variant.Type.INT, variant.getType());
+ assertEquals(42, variant.getInt());
+ }
+ }
+
+ @Test
+ void testNullType() {
+ VariantBuilder builder = new VariantBuilder();
+ builder.appendNull();
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.NULL, variant.getType());
+ }
+
+ @Test
+ void testBooleanType() {
+ VariantBuilder builder = new VariantBuilder();
+ builder.appendBoolean(true);
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.BOOLEAN, variant.getType());
+ assertTrue(variant.getBoolean());
+
+ builder = new VariantBuilder();
+ builder.appendBoolean(false);
+ variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.BOOLEAN, variant.getType());
+ assertFalse(variant.getBoolean());
+ }
+
+ @Test
+ void testByteType() {
+ VariantBuilder builder = new VariantBuilder();
+ builder.appendByte((byte) 42);
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.BYTE, variant.getType());
+ assertEquals((byte) 42, variant.getByte());
+ }
+
+ @Test
+ void testShortType() {
+ VariantBuilder builder = new VariantBuilder();
+ builder.appendShort((short) 1234);
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.SHORT, variant.getType());
+ assertEquals((short) 1234, variant.getShort());
+ }
+
+ @Test
+ void testIntType() {
+ VariantBuilder builder = new VariantBuilder();
+ builder.appendInt(123456);
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.INT, variant.getType());
+ assertEquals(123456, variant.getInt());
+ }
+
+ @Test
+ void testLongType() {
+ VariantBuilder builder = new VariantBuilder();
+ builder.appendLong(9876543210L);
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.LONG, variant.getType());
+ assertEquals(9876543210L, variant.getLong());
+ }
+
+ @Test
+ void testFloatType() {
+ VariantBuilder builder = new VariantBuilder();
+ builder.appendFloat(3.14f);
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.FLOAT, variant.getType());
+ assertEquals(3.14f, variant.getFloat(), 0.001f);
+ }
+
+ @Test
+ void testDoubleType() {
+ VariantBuilder builder = new VariantBuilder();
+ builder.appendDouble(3.14159265359);
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.DOUBLE, variant.getType());
+ assertEquals(3.14159265359, variant.getDouble(), 0.0000001);
+ }
+
+ @Test
+ void testStringType() {
+ VariantBuilder builder = new VariantBuilder();
+ builder.appendString("hello world");
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.STRING, variant.getType());
+ assertEquals("hello world", variant.getString());
+ }
+
+ @Test
+ void testDecimalType() {
+ VariantBuilder builder = new VariantBuilder();
+ builder.appendDecimal(new BigDecimal("123.456"));
+ Variant variant = buildVariant(builder);
+
+ assertTrue(
+ variant.getType() == Variant.Type.DECIMAL4
+ || variant.getType() == Variant.Type.DECIMAL8
+ || variant.getType() == Variant.Type.DECIMAL16);
+ assertEquals(new BigDecimal("123.456"), variant.getDecimal());
+ }
+
+ @Test
+ void testBinaryType() {
+ VariantBuilder builder = new VariantBuilder();
+ byte[] data = new byte[] {1, 2, 3, 4, 5};
+ builder.appendBinary(ByteBuffer.wrap(data));
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.BINARY, variant.getType());
+ ByteBuffer result = variant.getBinary();
+ byte[] resultBytes = new byte[result.remaining()];
+ result.get(resultBytes);
+ assertArrayEquals(data, resultBytes);
+ }
+
+ @Test
+ void testUuidType() {
+ VariantBuilder builder = new VariantBuilder();
+ UUID uuid = UUID.randomUUID();
+ builder.appendUUID(uuid);
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.UUID, variant.getType());
+ assertEquals(uuid, variant.getUUID());
+ }
+
+ @Test
+ void testDateType() {
+ VariantBuilder builder = new VariantBuilder();
+ int daysSinceEpoch = 19000;
+ builder.appendDate(daysSinceEpoch);
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.DATE, variant.getType());
+ }
+
+ @Test
+ void testTimestampTzType() {
+ VariantBuilder builder = new VariantBuilder();
+ long micros = System.currentTimeMillis() * 1000;
+ builder.appendTimestampTz(micros);
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.TIMESTAMP_TZ, variant.getType());
+ }
+
+ @Test
+ void testTimestampNtzType() {
+ VariantBuilder builder = new VariantBuilder();
+ long micros = System.currentTimeMillis() * 1000;
+ builder.appendTimestampNtz(micros);
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.TIMESTAMP_NTZ, variant.getType());
+ }
+
+ @Test
+ void testTimeType() {
+ VariantBuilder builder = new VariantBuilder();
+ long micros = 12345678L;
+ builder.appendTime(micros);
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.TIME, variant.getType());
+ }
+
+ @Test
+ void testObjectType() {
+ VariantBuilder builder = new VariantBuilder();
+ var objBuilder = builder.startObject();
+ objBuilder.appendKey("name");
+ objBuilder.appendString("test");
+ objBuilder.appendKey("value");
+ objBuilder.appendInt(42);
+ builder.endObject();
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.OBJECT, variant.getType());
+ assertEquals(2, variant.numObjectElements());
+
+ Variant nameField = variant.getFieldByKey("name");
+ assertNotNull(nameField);
+ assertEquals(Variant.Type.STRING, nameField.getType());
+ assertEquals("test", nameField.getString());
+
+ Variant valueField = variant.getFieldByKey("value");
+ assertNotNull(valueField);
+ assertEquals(Variant.Type.INT, valueField.getType());
+ assertEquals(42, valueField.getInt());
+
+ assertNull(variant.getFieldByKey("nonexistent"));
+
+ // Empty object
+ builder = new VariantBuilder();
+ builder.startObject();
+ builder.endObject();
+ Variant emptyObj = buildVariant(builder);
+ assertEquals(Variant.Type.OBJECT, emptyObj.getType());
+ assertEquals(0, emptyObj.numObjectElements());
+ }
+
+ @Test
+ void testObjectFieldAtIndex() {
+ VariantBuilder builder = new VariantBuilder();
+ var objBuilder = builder.startObject();
+ objBuilder.appendKey("alpha");
+ objBuilder.appendInt(1);
+ objBuilder.appendKey("beta");
+ objBuilder.appendInt(2);
+ builder.endObject();
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.OBJECT, variant.getType());
+ assertEquals(2, variant.numObjectElements());
+
+ Variant.ObjectField field0 = variant.getFieldAtIndex(0);
+ assertNotNull(field0);
+ assertNotNull(field0.key);
+ assertNotNull(field0.value);
+
+ Variant.ObjectField field1 = variant.getFieldAtIndex(1);
+ assertNotNull(field1);
+ assertNotNull(field1.key);
+ assertNotNull(field1.value);
+ }
+
+ @Test
+ void testArrayType() {
+ VariantBuilder builder = new VariantBuilder();
+ var arrayBuilder = builder.startArray();
+ arrayBuilder.appendInt(1);
+ arrayBuilder.appendInt(2);
+ arrayBuilder.appendInt(3);
+ builder.endArray();
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.ARRAY, variant.getType());
+ assertEquals(3, variant.numArrayElements());
+
+ Variant elem0 = variant.getElementAtIndex(0);
+ assertNotNull(elem0);
+ assertEquals(Variant.Type.INT, elem0.getType());
+ assertEquals(1, elem0.getInt());
+
+ Variant elem1 = variant.getElementAtIndex(1);
+ assertEquals(2, elem1.getInt());
+
+ Variant elem2 = variant.getElementAtIndex(2);
+ assertEquals(3, elem2.getInt());
+
+ assertNull(variant.getElementAtIndex(-1));
+ assertNull(variant.getElementAtIndex(3));
+
+ // Empty array
+ builder = new VariantBuilder();
+ builder.startArray();
+ builder.endArray();
+ Variant emptyArr = buildVariant(builder);
+ assertEquals(Variant.Type.ARRAY, emptyArr.getType());
+ assertEquals(0, emptyArr.numArrayElements());
+ }
+
+ @Test
+ void testNestedStructure() {
+ VariantBuilder builder = new VariantBuilder();
+ var objBuilder = builder.startObject();
+ objBuilder.appendKey("items");
+ var arrayBuilder = objBuilder.startArray();
+ arrayBuilder.appendString("a");
+ arrayBuilder.appendString("b");
+ objBuilder.endArray();
+ builder.endObject();
+ Variant variant = buildVariant(builder);
+
+ assertEquals(Variant.Type.OBJECT, variant.getType());
+ Variant items = variant.getFieldByKey("items");
+ assertNotNull(items);
+ assertEquals(Variant.Type.ARRAY, items.getType());
+ assertEquals(2, items.numArrayElements());
+ assertEquals("a", items.getElementAtIndex(0).getString());
+ assertEquals("b", items.getElementAtIndex(1).getString());
+ }
+
+ @Test
+ void testEquals() {
+ VariantBuilder builder1 = new VariantBuilder();
+ builder1.appendString("test");
+ Variant variant1 = buildVariant(builder1);
+
+ VariantBuilder builder2 = new VariantBuilder();
+ builder2.appendString("test");
+ Variant variant2 = buildVariant(builder2);
+
+ VariantBuilder builder3 = new VariantBuilder();
+ builder3.appendString("different");
+ Variant variant3 = buildVariant(builder3);
+
+ assertEquals(variant1, variant1);
+ assertEquals(variant1, variant2);
+ assertNotEquals(variant1, variant3);
+ assertNotEquals(variant1, null);
+ assertNotEquals(variant1, "not a variant");
+ }
+
+ @Test
+ void testHashCode() {
+ VariantBuilder builder1 = new VariantBuilder();
+ builder1.appendInt(42);
+ Variant variant1 = buildVariant(builder1);
+
+ VariantBuilder builder2 = new VariantBuilder();
+ builder2.appendInt(42);
+ Variant variant2 = buildVariant(builder2);
+
+ assertEquals(variant1.hashCode(), variant2.hashCode());
+ }
+
+ @Test
+ void testToString() {
+ VariantBuilder builder = new VariantBuilder();
+ builder.appendString("test");
+ Variant variant = buildVariant(builder);
+
+ String str = variant.toString();
+ assertNotNull(str);
+ assertTrue(str.contains("type="));
+ }
+
+ @Test
+ void testTypeEnumsMatch() {
+ for (Variant.Type arrowType : Variant.Type.values()) {
+ org.apache.parquet.variant.Variant.Type parquetType =
+ org.apache.parquet.variant.Variant.Type.valueOf(arrowType.name());
+ assertEquals(arrowType, Variant.Type.fromParquet(parquetType));
+ }
+ for (org.apache.parquet.variant.Variant.Type parquetType :
+ org.apache.parquet.variant.Variant.Type.values()) {
+ Variant.Type arrowType = Variant.Type.valueOf(parquetType.name());
+ assertEquals(parquetType.name(), arrowType.name());
+ }
+ }
+}
diff --git a/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantExtensionType.java b/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantExtensionType.java
new file mode 100644
index 0000000000..f3213d523a
--- /dev/null
+++ b/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantExtensionType.java
@@ -0,0 +1,249 @@
+/*
+ * 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.variant.extension;
+
+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.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Collections;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.variant.TestVariant;
+import org.apache.arrow.variant.Variant;
+import org.apache.arrow.vector.ExtensionTypeVector;
+import org.apache.arrow.vector.FieldVector;
+import org.apache.arrow.vector.VarBinaryVector;
+import org.apache.arrow.vector.VectorSchemaRoot;
+import org.apache.arrow.vector.compare.Range;
+import org.apache.arrow.vector.compare.RangeEqualsVisitor;
+import org.apache.arrow.vector.complex.StructVector;
+import org.apache.arrow.vector.complex.writer.BaseWriter;
+import org.apache.arrow.vector.ipc.ArrowFileReader;
+import org.apache.arrow.vector.ipc.ArrowFileWriter;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.ArrowType.ExtensionType;
+import org.apache.arrow.vector.types.pojo.ExtensionTypeRegistry;
+import org.apache.arrow.vector.types.pojo.Field;
+import org.apache.arrow.vector.types.pojo.FieldType;
+import org.apache.arrow.vector.types.pojo.Schema;
+import org.apache.arrow.vector.util.VectorBatchAppender;
+import org.apache.arrow.vector.validate.ValidateVectorVisitor;
+import org.junit.jupiter.api.Test;
+
+public class TestVariantExtensionType {
+
+ private static void ensureRegistered(ArrowType.ExtensionType type) {
+ if (ExtensionTypeRegistry.lookup(type.extensionName()) == null) {
+ ExtensionTypeRegistry.register(type);
+ }
+ }
+
+ @Test
+ public void roundtripVariant() throws IOException {
+ ensureRegistered(VariantType.INSTANCE);
+ final Schema schema =
+ new Schema(Collections.singletonList(Field.nullable("a", VariantType.INSTANCE)));
+ try (final BufferAllocator allocator = new RootAllocator(Integer.MAX_VALUE);
+ final VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) {
+ VariantVector vector = (VariantVector) root.getVector("a");
+ vector.allocateNew();
+
+ vector.setSafe(0, TestVariant.variantString("hello"));
+ vector.setSafe(1, TestVariant.variantString("world"));
+ vector.setValueCount(2);
+ root.setRowCount(2);
+
+ final File file = File.createTempFile("varianttest", ".arrow");
+ try (final WritableByteChannel channel =
+ FileChannel.open(Paths.get(file.getAbsolutePath()), StandardOpenOption.WRITE);
+ final ArrowFileWriter writer = new ArrowFileWriter(root, null, channel)) {
+ writer.start();
+ writer.writeBatch();
+ writer.end();
+ }
+
+ try (final SeekableByteChannel channel =
+ Files.newByteChannel(Paths.get(file.getAbsolutePath()));
+ final ArrowFileReader reader = new ArrowFileReader(channel, allocator)) {
+ reader.loadNextBatch();
+ final VectorSchemaRoot readerRoot = reader.getVectorSchemaRoot();
+ assertEquals(root.getSchema(), readerRoot.getSchema());
+
+ final Field field = readerRoot.getSchema().getFields().get(0);
+ final VariantType expectedType = VariantType.INSTANCE;
+ assertEquals(
+ field.getMetadata().get(ExtensionType.EXTENSION_METADATA_KEY_NAME),
+ expectedType.extensionName());
+ assertEquals(
+ field.getMetadata().get(ExtensionType.EXTENSION_METADATA_KEY_METADATA),
+ expectedType.serialize());
+
+ final ExtensionTypeVector deserialized =
+ (ExtensionTypeVector) readerRoot.getFieldVectors().get(0);
+ assertEquals(vector.getValueCount(), deserialized.getValueCount());
+ for (int i = 0; i < vector.getValueCount(); i++) {
+ assertEquals(vector.isNull(i), deserialized.isNull(i));
+ if (!vector.isNull(i)) {
+ assertEquals(vector.getObject(i), deserialized.getObject(i));
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ public void readVariantAsUnderlyingType() throws IOException {
+ ensureRegistered(VariantType.INSTANCE);
+ final Schema schema =
+ new Schema(Collections.singletonList(VariantVector.createVariantField("a")));
+ try (final BufferAllocator allocator = new RootAllocator(Integer.MAX_VALUE);
+ final VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) {
+ VariantVector vector = (VariantVector) root.getVector("a");
+ vector.allocateNew();
+
+ vector.setSafe(0, TestVariant.variantString("hello"));
+ vector.setValueCount(1);
+ root.setRowCount(1);
+
+ final File file = File.createTempFile("varianttest", ".arrow");
+ try (final WritableByteChannel channel =
+ FileChannel.open(Paths.get(file.getAbsolutePath()), StandardOpenOption.WRITE);
+ final ArrowFileWriter writer = new ArrowFileWriter(root, null, channel)) {
+ writer.start();
+ writer.writeBatch();
+ writer.end();
+ }
+
+ ExtensionTypeRegistry.unregister(VariantType.INSTANCE);
+
+ try (final SeekableByteChannel channel =
+ Files.newByteChannel(Paths.get(file.getAbsolutePath()));
+ final ArrowFileReader reader = new ArrowFileReader(channel, allocator)) {
+ reader.loadNextBatch();
+ VectorSchemaRoot readRoot = reader.getVectorSchemaRoot();
+
+ // Verify schema properties
+ assertEquals(1, readRoot.getSchema().getFields().size());
+ assertEquals("a", readRoot.getSchema().getFields().get(0).getName());
+ assertTrue(readRoot.getSchema().getFields().get(0).getType() instanceof ArrowType.Struct);
+
+ // Verify extension metadata is preserved
+ final Field field = readRoot.getSchema().getFields().get(0);
+ assertEquals(
+ VariantType.EXTENSION_NAME,
+ field.getMetadata().get(ExtensionType.EXTENSION_METADATA_KEY_NAME));
+ assertEquals("", field.getMetadata().get(ExtensionType.EXTENSION_METADATA_KEY_METADATA));
+
+ // Verify vector type and row count
+ assertEquals(1, readRoot.getRowCount());
+ FieldVector readVector = readRoot.getVector("a");
+ assertEquals(StructVector.class, readVector.getClass());
+
+ // Verify value count matches
+ StructVector structVector = (StructVector) readVector;
+ assertEquals(vector.getValueCount(), structVector.getValueCount());
+
+ // Verify the underlying data can be accessed from child vectors
+ VarBinaryVector metadataVector =
+ structVector.getChild(VariantVector.METADATA_VECTOR_NAME, VarBinaryVector.class);
+ VarBinaryVector valueVector =
+ structVector.getChild(VariantVector.VALUE_VECTOR_NAME, VarBinaryVector.class);
+ assertNotNull(metadataVector);
+ assertNotNull(valueVector);
+ assertEquals(1, metadataVector.getValueCount());
+ assertEquals(1, valueVector.getValueCount());
+ }
+ }
+ }
+
+ @Test
+ public void testVariantVectorCompare() {
+ VariantType variantType = VariantType.INSTANCE;
+ ExtensionTypeRegistry.register(variantType);
+ Variant hello = TestVariant.variantString("hello");
+ Variant world = TestVariant.variantString("world");
+ try (final BufferAllocator allocator = new RootAllocator(Integer.MAX_VALUE);
+ VariantVector a1 =
+ (VariantVector)
+ variantType.getNewVector("a", FieldType.nullable(variantType), allocator);
+ VariantVector a2 =
+ (VariantVector)
+ variantType.getNewVector("a", FieldType.nullable(variantType), allocator);
+ VariantVector bb =
+ (VariantVector)
+ variantType.getNewVector("a", FieldType.nullable(variantType), allocator)) {
+
+ ValidateVectorVisitor validateVisitor = new ValidateVectorVisitor();
+ validateVisitor.visit(a1, null);
+
+ a1.allocateNew();
+ a2.allocateNew();
+ bb.allocateNew();
+
+ a1.setSafe(0, hello);
+ a1.setSafe(1, world);
+ a1.setValueCount(2);
+
+ a2.setSafe(0, hello);
+ a2.setSafe(1, world);
+ a2.setValueCount(2);
+
+ bb.setSafe(0, world);
+ bb.setSafe(1, hello);
+ bb.setValueCount(2);
+
+ Range range = new Range(0, 0, a1.getValueCount());
+ RangeEqualsVisitor visitor = new RangeEqualsVisitor(a1, a2);
+ assertTrue(visitor.rangeEquals(range));
+
+ visitor = new RangeEqualsVisitor(a1, bb);
+ assertFalse(visitor.rangeEquals(range));
+
+ VectorBatchAppender.batchAppend(a1, a2, bb);
+ assertEquals(6, a1.getValueCount());
+ validateVisitor.visit(a1, null);
+ }
+ }
+
+ @Test
+ public void testVariantCopyAsValueThrowsException() {
+ ensureRegistered(VariantType.INSTANCE);
+ try (BufferAllocator allocator = new RootAllocator(Integer.MAX_VALUE);
+ VariantVector vector = new VariantVector("variant", allocator)) {
+ vector.allocateNew();
+ vector.setSafe(0, TestVariant.variantString("hello"));
+ vector.setValueCount(1);
+
+ var reader = vector.getReader();
+ reader.setPosition(0);
+
+ assertThrows(
+ IllegalArgumentException.class, () -> reader.copyAsValue((BaseWriter.StructWriter) null));
+ }
+ }
+}
diff --git a/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantInListVector.java b/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantInListVector.java
new file mode 100644
index 0000000000..8b6000bc46
--- /dev/null
+++ b/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantInListVector.java
@@ -0,0 +1,202 @@
+/*
+ * 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.variant.extension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.variant.TestVariant;
+import org.apache.arrow.variant.Variant;
+import org.apache.arrow.variant.holders.NullableVariantHolder;
+import org.apache.arrow.vector.complex.ListVector;
+import org.apache.arrow.vector.complex.impl.UnionListReader;
+import org.apache.arrow.vector.complex.impl.UnionListWriter;
+import org.apache.arrow.vector.complex.reader.FieldReader;
+import org.apache.arrow.vector.complex.writer.BaseWriter.ExtensionWriter;
+import org.apache.arrow.vector.types.pojo.FieldType;
+import org.apache.arrow.vector.util.TransferPair;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TestVariantInListVector {
+
+ private BufferAllocator allocator;
+
+ @BeforeEach
+ public void init() {
+ allocator = new RootAllocator(Long.MAX_VALUE);
+ }
+
+ @AfterEach
+ public void terminate() throws Exception {
+ allocator.close();
+ }
+
+ @Test
+ public void testListVectorWithVariantExtensionType() {
+ final FieldType type = FieldType.nullable(VariantType.INSTANCE);
+ try (ListVector inVector = new ListVector("input", allocator, type, null)) {
+ Variant variant1 = TestVariant.variantString("hello");
+ Variant variant2 = TestVariant.variantString("bye");
+
+ UnionListWriter writer = inVector.getWriter();
+ writer.allocate();
+
+ writer.setPosition(0);
+ writer.startList();
+ ExtensionWriter extensionWriter = writer.extension(VariantType.INSTANCE);
+ extensionWriter.writeExtension(variant1);
+ extensionWriter.writeExtension(variant2);
+ writer.endList();
+ inVector.setValueCount(1);
+
+ ArrayList resultSet = (ArrayList) inVector.getObject(0);
+ assertEquals(2, resultSet.size());
+ assertEquals(variant1, resultSet.get(0));
+ assertEquals(variant2, resultSet.get(1));
+ }
+ }
+
+ @Test
+ public void testListVectorReaderForVariantExtensionType() {
+ try (ListVector inVector = ListVector.empty("input", allocator)) {
+ Variant variant1 = TestVariant.variantString("hello");
+ Variant variant2 = TestVariant.variantString("bye");
+
+ UnionListWriter writer = inVector.getWriter();
+ writer.allocate();
+
+ writer.setPosition(0);
+ writer.startList();
+ ExtensionWriter extensionWriter = writer.extension(VariantType.INSTANCE);
+ extensionWriter.writeExtension(variant1);
+ writer.endList();
+
+ writer.setPosition(1);
+ writer.startList();
+ extensionWriter.writeExtension(variant2);
+ extensionWriter.writeExtension(variant2);
+ writer.endList();
+
+ inVector.setValueCount(2);
+
+ UnionListReader reader = inVector.getReader();
+ reader.setPosition(0);
+ assertTrue(reader.next());
+ FieldReader variantReader = reader.reader();
+ NullableVariantHolder resultHolder = new NullableVariantHolder();
+ variantReader.read(resultHolder);
+ assertEquals(variant1, new Variant(resultHolder));
+
+ reader.setPosition(1);
+ assertTrue(reader.next());
+ variantReader = reader.reader();
+ variantReader.read(resultHolder);
+ assertEquals(variant2, new Variant(resultHolder));
+
+ assertTrue(reader.next());
+ variantReader = reader.reader();
+ variantReader.read(resultHolder);
+ assertEquals(variant2, new Variant(resultHolder));
+ }
+ }
+
+ @Test
+ public void testCopyFromForVariantExtensionType() {
+ try (ListVector inVector = ListVector.empty("input", allocator);
+ ListVector outVector = ListVector.empty("output", allocator)) {
+ Variant variant1 = TestVariant.variantString("hello");
+ Variant variant2 = TestVariant.variantString("bye");
+
+ UnionListWriter writer = inVector.getWriter();
+ writer.allocate();
+
+ writer.setPosition(0);
+ writer.startList();
+ ExtensionWriter extensionWriter = writer.extension(VariantType.INSTANCE);
+ extensionWriter.writeExtension(variant1);
+ writer.endList();
+
+ writer.setPosition(1);
+ writer.startList();
+ extensionWriter.writeExtension(variant2);
+ extensionWriter.writeExtension(variant2);
+ writer.endList();
+
+ inVector.setValueCount(2);
+
+ outVector.allocateNew();
+ outVector.copyFrom(0, 0, inVector);
+ outVector.copyFrom(1, 1, inVector);
+ outVector.setValueCount(2);
+
+ ArrayList resultSet0 = (ArrayList) outVector.getObject(0);
+ assertEquals(1, resultSet0.size());
+ assertEquals(variant1, resultSet0.get(0));
+
+ ArrayList resultSet1 = (ArrayList) outVector.getObject(1);
+ assertEquals(2, resultSet1.size());
+ assertEquals(variant2, resultSet1.get(0));
+ assertEquals(variant2, resultSet1.get(1));
+ }
+ }
+
+ @Test
+ public void testCopyValueSafeForVariantExtensionType() {
+ try (ListVector inVector = ListVector.empty("input", allocator)) {
+ Variant variant1 = TestVariant.variantString("hello");
+ Variant variant2 = TestVariant.variantString("bye");
+
+ UnionListWriter writer = inVector.getWriter();
+ writer.allocate();
+
+ writer.setPosition(0);
+ writer.startList();
+ ExtensionWriter extensionWriter = writer.extension(VariantType.INSTANCE);
+ extensionWriter.writeExtension(variant1);
+ writer.endList();
+
+ writer.setPosition(1);
+ writer.startList();
+ extensionWriter.writeExtension(variant2);
+ extensionWriter.writeExtension(variant2);
+ writer.endList();
+
+ inVector.setValueCount(2);
+
+ try (ListVector outVector = (ListVector) inVector.getTransferPair(allocator).getTo()) {
+ TransferPair tp = inVector.makeTransferPair(outVector);
+ tp.copyValueSafe(0, 0);
+ tp.copyValueSafe(1, 1);
+ outVector.setValueCount(2);
+
+ ArrayList resultSet0 = (ArrayList) outVector.getObject(0);
+ assertEquals(1, resultSet0.size());
+ assertEquals(variant1, resultSet0.get(0));
+
+ ArrayList resultSet1 = (ArrayList) outVector.getObject(1);
+ assertEquals(2, resultSet1.size());
+ assertEquals(variant2, resultSet1.get(0));
+ assertEquals(variant2, resultSet1.get(1));
+ }
+ }
+ }
+}
diff --git a/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantInMapVector.java b/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantInMapVector.java
new file mode 100644
index 0000000000..dd925810de
--- /dev/null
+++ b/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantInMapVector.java
@@ -0,0 +1,125 @@
+/*
+ * 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.variant.extension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.variant.TestVariant;
+import org.apache.arrow.variant.Variant;
+import org.apache.arrow.variant.holders.NullableVariantHolder;
+import org.apache.arrow.vector.complex.MapVector;
+import org.apache.arrow.vector.complex.impl.UnionMapReader;
+import org.apache.arrow.vector.complex.impl.UnionMapWriter;
+import org.apache.arrow.vector.complex.reader.FieldReader;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TestVariantInMapVector {
+
+ private BufferAllocator allocator;
+
+ @BeforeEach
+ public void init() {
+ allocator = new RootAllocator(Long.MAX_VALUE);
+ }
+
+ @AfterEach
+ public void terminate() {
+ allocator.close();
+ }
+
+ @Test
+ public void testMapVectorWithVariantExtensionType() {
+ Variant variant1 = TestVariant.variantString("hello");
+ Variant variant2 = TestVariant.variantString("world");
+ try (final MapVector inVector = MapVector.empty("map", allocator, false)) {
+ inVector.allocateNew();
+ UnionMapWriter writer = inVector.getWriter();
+ writer.setPosition(0);
+
+ writer.startMap();
+ writer.startEntry();
+ writer.key().bigInt().writeBigInt(0);
+ writer.value().extension(VariantType.INSTANCE).writeExtension(variant1, VariantType.INSTANCE);
+ writer.endEntry();
+ writer.startEntry();
+ writer.key().bigInt().writeBigInt(1);
+ writer.value().extension(VariantType.INSTANCE).writeExtension(variant2, VariantType.INSTANCE);
+ writer.endEntry();
+ writer.endMap();
+
+ writer.setValueCount(1);
+
+ UnionMapReader mapReader = inVector.getReader();
+ mapReader.setPosition(0);
+ mapReader.next();
+ FieldReader variantReader = mapReader.value();
+ NullableVariantHolder holder = new NullableVariantHolder();
+ variantReader.read(holder);
+ assertEquals(variant1, new Variant(holder));
+
+ mapReader.next();
+ variantReader = mapReader.value();
+ variantReader.read(holder);
+ assertEquals(variant2, new Variant(holder));
+ }
+ }
+
+ @Test
+ public void testCopyFromForVariantExtensionType() {
+ Variant variant1 = TestVariant.variantString("hello");
+ Variant variant2 = TestVariant.variantString("world");
+ try (final MapVector inVector = MapVector.empty("in", allocator, false);
+ final MapVector outVector = MapVector.empty("out", allocator, false)) {
+ inVector.allocateNew();
+ UnionMapWriter writer = inVector.getWriter();
+ writer.setPosition(0);
+
+ writer.startMap();
+ writer.startEntry();
+ writer.key().bigInt().writeBigInt(0);
+ writer.value().extension(VariantType.INSTANCE).writeExtension(variant1, VariantType.INSTANCE);
+ writer.endEntry();
+ writer.startEntry();
+ writer.key().bigInt().writeBigInt(1);
+ writer.value().extension(VariantType.INSTANCE).writeExtension(variant2, VariantType.INSTANCE);
+ writer.endEntry();
+ writer.endMap();
+
+ writer.setValueCount(1);
+ outVector.allocateNew();
+ outVector.copyFrom(0, 0, inVector);
+ outVector.setValueCount(1);
+
+ UnionMapReader mapReader = outVector.getReader();
+ mapReader.setPosition(0);
+ mapReader.next();
+ FieldReader variantReader = mapReader.value();
+ NullableVariantHolder holder = new NullableVariantHolder();
+ variantReader.read(holder);
+ assertEquals(variant1, new Variant(holder));
+
+ mapReader.next();
+ variantReader = mapReader.value();
+ variantReader.read(holder);
+ assertEquals(variant2, new Variant(holder));
+ }
+ }
+}
diff --git a/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantType.java b/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantType.java
new file mode 100644
index 0000000000..017e71224b
--- /dev/null
+++ b/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantType.java
@@ -0,0 +1,308 @@
+/*
+ * 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.variant.extension;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.arrow.memory.ArrowBuf;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.variant.holders.NullableVariantHolder;
+import org.apache.arrow.vector.FieldVector;
+import org.apache.arrow.vector.VectorSchemaRoot;
+import org.apache.arrow.vector.dictionary.DictionaryProvider;
+import org.apache.arrow.vector.ipc.ArrowStreamReader;
+import org.apache.arrow.vector.ipc.ArrowStreamWriter;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.ExtensionTypeRegistry;
+import org.apache.arrow.vector.types.pojo.Field;
+import org.apache.arrow.vector.types.pojo.FieldType;
+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;
+
+class TestVariantType {
+ BufferAllocator allocator;
+
+ @BeforeEach
+ void beforeEach() {
+ allocator = new RootAllocator();
+ }
+
+ @AfterEach
+ void afterEach() {
+ allocator.close();
+ }
+
+ @Test
+ void testConstants() {
+ assertNotNull(VariantType.INSTANCE);
+ }
+
+ @Test
+ void testStorageType() {
+ VariantType type = VariantType.INSTANCE;
+ assertEquals(ArrowType.Struct.INSTANCE, type.storageType());
+ assertInstanceOf(ArrowType.Struct.class, type.storageType());
+ }
+
+ @Test
+ void testExtensionName() {
+ VariantType type = VariantType.INSTANCE;
+ assertEquals("parquet.variant", type.extensionName());
+ }
+
+ @Test
+ void testExtensionEquals() {
+ VariantType type1 = VariantType.INSTANCE;
+ VariantType type2 = VariantType.INSTANCE;
+
+ assertTrue(type1.extensionEquals(type2));
+ }
+
+ @Test
+ void testIsComplex() {
+ VariantType type = VariantType.INSTANCE;
+ assertFalse(type.isComplex());
+ }
+
+ @Test
+ void testSerialize() {
+ VariantType type = VariantType.INSTANCE;
+ String serialized = type.serialize();
+ assertEquals("", serialized);
+ }
+
+ @Test
+ void testDeserializeValid() {
+ VariantType type = VariantType.INSTANCE;
+ ArrowType storageType = ArrowType.Struct.INSTANCE;
+
+ ArrowType deserialized = assertDoesNotThrow(() -> type.deserialize(storageType, ""));
+ assertInstanceOf(VariantType.class, deserialized);
+ assertEquals(VariantType.INSTANCE, deserialized);
+ }
+
+ @Test
+ void testDeserializeInvalidStorageType() {
+ VariantType type = VariantType.INSTANCE;
+ ArrowType wrongStorageType = ArrowType.Utf8.INSTANCE;
+
+ assertThrows(UnsupportedOperationException.class, () -> type.deserialize(wrongStorageType, ""));
+ }
+
+ @Test
+ void testGetNewVector() {
+ VariantType type = VariantType.INSTANCE;
+ try (FieldVector vector =
+ type.getNewVector("variant_field", FieldType.nullable(type), allocator)) {
+ assertInstanceOf(VariantVector.class, vector);
+ assertEquals("variant_field", vector.getField().getName());
+ assertEquals(type, vector.getField().getType());
+ }
+ }
+
+ @Test
+ void testGetNewVectorWithNullableFieldType() {
+ VariantType type = VariantType.INSTANCE;
+ FieldType nullableFieldType = FieldType.nullable(type);
+
+ try (FieldVector vector = type.getNewVector("nullable_variant", nullableFieldType, allocator)) {
+ assertInstanceOf(VariantVector.class, vector);
+ assertEquals("nullable_variant", vector.getField().getName());
+ assertTrue(vector.getField().isNullable());
+ }
+ }
+
+ @Test
+ void testGetNewVectorWithNonNullableFieldType() {
+ VariantType type = VariantType.INSTANCE;
+ FieldType nonNullableFieldType = FieldType.notNullable(type);
+
+ try (FieldVector vector =
+ type.getNewVector("non_nullable_variant", nonNullableFieldType, allocator)) {
+ assertInstanceOf(VariantVector.class, vector);
+ assertEquals("non_nullable_variant", vector.getField().getName());
+ }
+ }
+
+ @Test
+ void testIpcRoundTrip() {
+ VariantType type = VariantType.INSTANCE;
+
+ Schema schema = new Schema(Collections.singletonList(Field.nullable("variant", type)));
+ byte[] serialized = schema.serializeAsMessage();
+ Schema deserialized = Schema.deserializeMessage(ByteBuffer.wrap(serialized));
+ assertEquals(schema, deserialized);
+ }
+
+ @Test
+ void testVectorIpcRoundTrip() throws IOException {
+ VariantType type = VariantType.INSTANCE;
+
+ try (FieldVector vector = type.getNewVector("field", FieldType.nullable(type), allocator);
+ ArrowBuf metadataBuf1 = allocator.buffer(10);
+ ArrowBuf valueBuf1 = allocator.buffer(10);
+ ArrowBuf metadataBuf2 = allocator.buffer(10);
+ ArrowBuf valueBuf2 = allocator.buffer(10)) {
+ VariantVector variantVector = (VariantVector) vector;
+
+ byte[] metadata1 = new byte[] {1, 2, 3};
+ byte[] value1 = new byte[] {4, 5, 6, 7};
+ metadataBuf1.setBytes(0, metadata1);
+ valueBuf1.setBytes(0, value1);
+
+ byte[] metadata2 = new byte[] {8, 9};
+ byte[] value2 = new byte[] {10, 11, 12};
+ metadataBuf2.setBytes(0, metadata2);
+ valueBuf2.setBytes(0, value2);
+
+ NullableVariantHolder holder1 = new NullableVariantHolder();
+ holder1.isSet = 1;
+ holder1.metadataStart = 0;
+ holder1.metadataEnd = metadata1.length;
+ holder1.metadataBuffer = metadataBuf1;
+ holder1.valueStart = 0;
+ holder1.valueEnd = value1.length;
+ holder1.valueBuffer = valueBuf1;
+
+ NullableVariantHolder holder2 = new NullableVariantHolder();
+ holder2.isSet = 1;
+ holder2.metadataStart = 0;
+ holder2.metadataEnd = metadata2.length;
+ holder2.metadataBuffer = metadataBuf2;
+ holder2.valueStart = 0;
+ holder2.valueEnd = value2.length;
+ holder2.valueBuffer = valueBuf2;
+
+ variantVector.setSafe(0, holder1);
+ variantVector.setNull(1);
+ variantVector.setSafe(2, holder2);
+ variantVector.setValueCount(3);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (VectorSchemaRoot root = new VectorSchemaRoot(Collections.singletonList(variantVector));
+ ArrowStreamWriter writer =
+ new ArrowStreamWriter(root, new DictionaryProvider.MapDictionaryProvider(), baos)) {
+ writer.start();
+ writer.writeBatch();
+ }
+
+ try (ArrowStreamReader reader =
+ new ArrowStreamReader(new ByteArrayInputStream(baos.toByteArray()), allocator)) {
+ assertTrue(reader.loadNextBatch());
+ VectorSchemaRoot root = reader.getVectorSchemaRoot();
+ assertEquals(3, root.getRowCount());
+ assertEquals(
+ new Schema(Collections.singletonList(variantVector.getField())), root.getSchema());
+
+ VariantVector actual = assertInstanceOf(VariantVector.class, root.getVector("field"));
+ assertFalse(actual.isNull(0));
+ assertTrue(actual.isNull(1));
+ assertFalse(actual.isNull(2));
+
+ NullableVariantHolder result1 = new NullableVariantHolder();
+ actual.get(0, result1);
+ assertEquals(1, result1.isSet);
+ assertEquals(metadata1.length, result1.metadataEnd - result1.metadataStart);
+ assertEquals(value1.length, result1.valueEnd - result1.valueStart);
+
+ assertNull(actual.getObject(1));
+
+ NullableVariantHolder result2 = new NullableVariantHolder();
+ actual.get(2, result2);
+ assertEquals(1, result2.isSet);
+ assertEquals(metadata2.length, result2.metadataEnd - result2.metadataStart);
+ assertEquals(value2.length, result2.valueEnd - result2.valueStart);
+ }
+ }
+ }
+
+ @Test
+ void testSingleton() {
+ VariantType type1 = VariantType.INSTANCE;
+ VariantType type2 = VariantType.INSTANCE;
+
+ // Same instance
+ assertSame(type1, type2);
+ assertTrue(type1.extensionEquals(type2));
+ }
+
+ @Test
+ void testExtensionTypeRegistry() {
+ // VariantType should be automatically registered via static initializer
+ ArrowType.ExtensionType registeredType =
+ ExtensionTypeRegistry.lookup(VariantType.EXTENSION_NAME);
+ assertNotNull(registeredType);
+ assertInstanceOf(VariantType.class, registeredType);
+ assertEquals(VariantType.INSTANCE, registeredType);
+ }
+
+ @Test
+ void testFieldMetadata() {
+ Map metadata = new HashMap<>();
+ metadata.put("key1", "value1");
+ metadata.put("key2", "value2");
+
+ FieldType fieldType = new FieldType(true, VariantType.INSTANCE, null, metadata);
+ try (VariantVector vector = new VariantVector("test", allocator)) {
+ Field field = new Field("test", fieldType, VariantVector.createVariantChildFields());
+
+ // Field metadata includes both custom metadata and extension type metadata
+ Map fieldMetadata = field.getMetadata();
+ assertEquals("value1", fieldMetadata.get("key1"));
+ assertEquals("value2", fieldMetadata.get("key2"));
+ // Extension type metadata is also present
+ assertTrue(fieldMetadata.containsKey("ARROW:extension:name"));
+ assertTrue(fieldMetadata.containsKey("ARROW:extension:metadata"));
+ }
+ }
+
+ @Test
+ void testFieldChildren() {
+ try (VariantVector vector = new VariantVector("test", allocator)) {
+ Field field = vector.getField();
+
+ assertNotNull(field.getChildren());
+ assertEquals(2, field.getChildren().size());
+
+ Field metadataField = field.getChildren().get(0);
+ assertEquals(VariantVector.METADATA_VECTOR_NAME, metadataField.getName());
+ assertEquals(ArrowType.Binary.INSTANCE, metadataField.getType());
+
+ Field valueField = field.getChildren().get(1);
+ assertEquals(VariantVector.VALUE_VECTOR_NAME, valueField.getName());
+ assertEquals(ArrowType.Binary.INSTANCE, valueField.getType());
+ }
+ }
+}
diff --git a/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantVector.java b/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantVector.java
new file mode 100644
index 0000000000..1c172e304f
--- /dev/null
+++ b/arrow-variant/src/test/java/org/apache/arrow/variant/extension/TestVariantVector.java
@@ -0,0 +1,844 @@
+/*
+ * 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.variant.extension;
+
+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.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.arrow.memory.ArrowBuf;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.variant.Variant;
+import org.apache.arrow.variant.holders.NullableVariantHolder;
+import org.apache.arrow.variant.holders.VariantHolder;
+import org.apache.arrow.variant.impl.VariantReaderImpl;
+import org.apache.arrow.variant.impl.VariantWriterImpl;
+import org.apache.arrow.vector.holders.ExtensionHolder;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** Tests for VariantVector, VariantWriterImpl, and VariantReaderImpl. */
+class TestVariantVector {
+
+ private BufferAllocator allocator;
+
+ @BeforeEach
+ void beforeEach() {
+ allocator = new RootAllocator();
+ }
+
+ @AfterEach
+ void afterEach() {
+ allocator.close();
+ }
+
+ private VariantHolder createHolder(
+ ArrowBuf metadataBuf, byte[] metadata, ArrowBuf valueBuf, byte[] value) {
+ VariantHolder holder = new VariantHolder();
+ holder.metadataStart = 0;
+ holder.metadataEnd = metadata.length;
+ holder.metadataBuffer = metadataBuf;
+ holder.valueStart = 0;
+ holder.valueEnd = value.length;
+ holder.valueBuffer = valueBuf;
+ return holder;
+ }
+
+ private NullableVariantHolder createNullableHolder(
+ ArrowBuf metadataBuf, byte[] metadata, ArrowBuf valueBuf, byte[] value) {
+ NullableVariantHolder holder = new NullableVariantHolder();
+ holder.isSet = 1;
+ holder.metadataStart = 0;
+ holder.metadataEnd = metadata.length;
+ holder.metadataBuffer = metadataBuf;
+ holder.valueStart = 0;
+ holder.valueEnd = value.length;
+ holder.valueBuffer = valueBuf;
+ return holder;
+ }
+
+ private NullableVariantHolder createNullHolder() {
+ NullableVariantHolder holder = new NullableVariantHolder();
+ holder.isSet = 0;
+ return holder;
+ }
+
+ // ========== Basic Vector Tests ==========
+
+ @Test
+ void testVectorCreation() {
+ try (VariantVector vector = new VariantVector("test", allocator)) {
+ assertNotNull(vector);
+ assertEquals("test", vector.getField().getName());
+ assertNotNull(vector.getMetadataVector());
+ assertNotNull(vector.getValueVector());
+ }
+ }
+
+ @Test
+ void testSetAndGet() {
+ try (VariantVector vector = new VariantVector("test", allocator);
+ ArrowBuf metadataBuf = allocator.buffer(10);
+ ArrowBuf valueBuf = allocator.buffer(10)) {
+
+ byte[] metadata = new byte[] {1, 2, 3};
+ byte[] value = new byte[] {4, 5, 6, 7};
+ metadataBuf.setBytes(0, metadata);
+ valueBuf.setBytes(0, value);
+
+ NullableVariantHolder holder = createNullableHolder(metadataBuf, metadata, valueBuf, value);
+
+ vector.setSafe(0, holder);
+ vector.setValueCount(1);
+
+ // Retrieve and verify
+ NullableVariantHolder result = new NullableVariantHolder();
+ vector.get(0, result);
+
+ assertEquals(1, result.isSet);
+ assertEquals(metadata.length, result.metadataEnd - result.metadataStart);
+ assertEquals(value.length, result.valueEnd - result.valueStart);
+
+ byte[] actualMetadata = new byte[metadata.length];
+ byte[] actualValue = new byte[value.length];
+ result.metadataBuffer.getBytes(result.metadataStart, actualMetadata);
+ result.valueBuffer.getBytes(result.valueStart, actualValue);
+
+ assertArrayEquals(metadata, actualMetadata);
+ assertArrayEquals(value, actualValue);
+ }
+ }
+
+ @Test
+ void testSetNull() {
+ try (VariantVector vector = new VariantVector("test", allocator)) {
+ NullableVariantHolder holder = createNullHolder();
+
+ vector.setSafe(0, holder);
+ vector.setValueCount(1);
+
+ assertTrue(vector.isNull(0));
+
+ NullableVariantHolder result = new NullableVariantHolder();
+ vector.get(0, result);
+ assertEquals(0, result.isSet);
+ }
+ }
+
+ @Test
+ void testMultipleValues() {
+ try (VariantVector vector = new VariantVector("test", allocator);
+ ArrowBuf metadataBuf1 = allocator.buffer(10);
+ ArrowBuf valueBuf1 = allocator.buffer(10);
+ ArrowBuf metadataBuf2 = allocator.buffer(10);
+ ArrowBuf valueBuf2 = allocator.buffer(10)) {
+
+ byte[] metadata1 = new byte[] {1, 2};
+ byte[] value1 = new byte[] {3, 4, 5};
+ metadataBuf1.setBytes(0, metadata1);
+ valueBuf1.setBytes(0, value1);
+
+ NullableVariantHolder holder1 =
+ createNullableHolder(metadataBuf1, metadata1, valueBuf1, value1);
+
+ byte[] metadata2 = new byte[] {6, 7, 8};
+ byte[] value2 = new byte[] {9, 10};
+ metadataBuf2.setBytes(0, metadata2);
+ valueBuf2.setBytes(0, value2);
+
+ NullableVariantHolder holder2 =
+ createNullableHolder(metadataBuf2, metadata2, valueBuf2, value2);
+
+ vector.setSafe(0, holder1);
+ vector.setSafe(1, holder2);
+ vector.setValueCount(2);
+
+ // Verify first value
+ NullableVariantHolder result1 = new NullableVariantHolder();
+ vector.get(0, result1);
+ assertEquals(1, result1.isSet);
+
+ byte[] actualMetadata1 = new byte[metadata1.length];
+ byte[] actualValue1 = new byte[value1.length];
+ result1.metadataBuffer.getBytes(result1.metadataStart, actualMetadata1);
+ result1.valueBuffer.getBytes(result1.valueStart, actualValue1);
+ assertArrayEquals(metadata1, actualMetadata1);
+ assertArrayEquals(value1, actualValue1);
+
+ // Verify second value
+ NullableVariantHolder result2 = new NullableVariantHolder();
+ vector.get(1, result2);
+ assertEquals(1, result2.isSet);
+
+ byte[] actualMetadata2 = new byte[metadata2.length];
+ byte[] actualValue2 = new byte[value2.length];
+ result2.metadataBuffer.getBytes(result2.metadataStart, actualMetadata2);
+ result2.valueBuffer.getBytes(result2.valueStart, actualValue2);
+ assertArrayEquals(metadata2, actualMetadata2);
+ assertArrayEquals(value2, actualValue2);
+ }
+ }
+
+ @Test
+ void testNonNullableHolder() {
+ try (VariantVector vector = new VariantVector("test", allocator);
+ ArrowBuf metadataBuf = allocator.buffer(10);
+ ArrowBuf valueBuf = allocator.buffer(10)) {
+
+ byte[] metadata = new byte[] {1, 2, 3};
+ byte[] value = new byte[] {4, 5, 6};
+ metadataBuf.setBytes(0, metadata);
+ valueBuf.setBytes(0, value);
+
+ VariantHolder holder = createHolder(metadataBuf, metadata, valueBuf, value);
+
+ vector.setSafe(0, holder);
+ vector.setValueCount(1);
+
+ assertFalse(vector.isNull(0));
+
+ NullableVariantHolder result = new NullableVariantHolder();
+ vector.get(0, result);
+ assertEquals(1, result.isSet);
+ }
+ }
+
+ // ========== Writer Tests ==========
+
+ @Test
+ void testWriteWithVariantHolder() {
+ try (VariantVector vector = new VariantVector("test", allocator);
+ VariantWriterImpl writer = new VariantWriterImpl(vector);
+ ArrowBuf metadataBuf = allocator.buffer(10);
+ ArrowBuf valueBuf = allocator.buffer(10)) {
+
+ byte[] metadata = new byte[] {1, 2};
+ byte[] value = new byte[] {3, 4, 5};
+ metadataBuf.setBytes(0, metadata);
+ valueBuf.setBytes(0, value);
+
+ VariantHolder holder = createHolder(metadataBuf, metadata, valueBuf, value);
+
+ writer.setPosition(0);
+ writer.write(holder);
+
+ assertEquals(1, vector.getValueCount());
+ assertFalse(vector.isNull(0));
+ }
+ }
+
+ @Test
+ void testWriteWithNullableVariantHolder() {
+ try (VariantVector vector = new VariantVector("test", allocator);
+ VariantWriterImpl writer = new VariantWriterImpl(vector);
+ ArrowBuf metadataBuf = allocator.buffer(10);
+ ArrowBuf valueBuf = allocator.buffer(10)) {
+
+ byte[] metadata = new byte[] {1, 2};
+ byte[] value = new byte[] {3, 4, 5};
+ metadataBuf.setBytes(0, metadata);
+ valueBuf.setBytes(0, value);
+
+ NullableVariantHolder holder = createNullableHolder(metadataBuf, metadata, valueBuf, value);
+
+ writer.setPosition(0);
+ writer.write(holder);
+
+ assertEquals(1, vector.getValueCount());
+ assertFalse(vector.isNull(0));
+ }
+ }
+
+ @Test
+ void testWriteWithNullableVariantHolderNull() {
+ try (VariantVector vector = new VariantVector("test", allocator);
+ VariantWriterImpl writer = new VariantWriterImpl(vector)) {
+
+ NullableVariantHolder holder = createNullHolder();
+
+ writer.setPosition(0);
+ writer.write(holder);
+
+ assertEquals(1, vector.getValueCount());
+ assertTrue(vector.isNull(0));
+ }
+ }
+
+ @Test
+ void testWriteExtensionWithUnsupportedType() {
+ try (VariantVector vector = new VariantVector("test", allocator);
+ VariantWriterImpl writer = new VariantWriterImpl(vector)) {
+
+ writer.setPosition(0);
+
+ IllegalArgumentException exception =
+ assertThrows(IllegalArgumentException.class, () -> writer.writeExtension("invalid-type"));
+
+ assertTrue(exception.getMessage().contains("Unsupported type for Variant"));
+ }
+ }
+
+ @Test
+ void testWriteWithUnsupportedHolder() {
+ try (VariantVector vector = new VariantVector("test", allocator);
+ VariantWriterImpl writer = new VariantWriterImpl(vector)) {
+
+ ExtensionHolder unsupportedHolder =
+ new ExtensionHolder() {
+ @Override
+ public ArrowType type() {
+ return VariantType.INSTANCE;
+ }
+ };
+
+ writer.setPosition(0);
+
+ IllegalArgumentException exception =
+ assertThrows(IllegalArgumentException.class, () -> writer.write(unsupportedHolder));
+
+ assertTrue(exception.getMessage().contains("Unsupported type for Variant"));
+ }
+ }
+
+ // ========== Reader Tests ==========
+
+ @Test
+ void testReaderReadWithNullableVariantHolder() {
+ try (VariantVector vector = new VariantVector("test", allocator);
+ ArrowBuf metadataBuf = allocator.buffer(10);
+ ArrowBuf valueBuf = allocator.buffer(10)) {
+
+ byte[] metadata = new byte[] {1, 2, 3};
+ byte[] value = new byte[] {4, 5, 6};
+ metadataBuf.setBytes(0, metadata);
+ valueBuf.setBytes(0, value);
+
+ NullableVariantHolder holder = createNullableHolder(metadataBuf, metadata, valueBuf, value);
+
+ vector.setSafe(0, holder);
+ vector.setValueCount(1);
+
+ VariantReaderImpl reader = (VariantReaderImpl) vector.getReader();
+ reader.setPosition(0);
+
+ NullableVariantHolder result = new NullableVariantHolder();
+ reader.read(result);
+
+ assertEquals(1, result.isSet);
+ assertEquals(metadata.length, result.metadataEnd - result.metadataStart);
+ assertEquals(value.length, result.valueEnd - result.valueStart);
+ }
+ }
+
+ @Test
+ void testReaderReadWithNullableVariantHolderNull() {
+ try (VariantVector vector = new VariantVector("test", allocator)) {
+ vector.setNull(0);
+ vector.setValueCount(1);
+
+ VariantReaderImpl reader = (VariantReaderImpl) vector.getReader();
+ reader.setPosition(0);
+
+ NullableVariantHolder holder = new NullableVariantHolder();
+ reader.read(holder);
+
+ assertEquals(0, holder.isSet);
+ }
+ }
+
+ @Test
+ void testReaderIsSet() {
+ try (VariantVector vector = new VariantVector("test", allocator);
+ ArrowBuf metadataBuf = allocator.buffer(10);
+ ArrowBuf valueBuf = allocator.buffer(10)) {
+
+ byte[] metadata = new byte[] {1};
+ byte[] value = new byte[] {2};
+ metadataBuf.setBytes(0, metadata);
+ valueBuf.setBytes(0, value);
+
+ NullableVariantHolder holder = createNullableHolder(metadataBuf, metadata, valueBuf, value);
+
+ vector.setSafe(0, holder);
+ vector.setNull(1);
+ vector.setValueCount(2);
+
+ VariantReaderImpl reader = (VariantReaderImpl) vector.getReader();
+
+ reader.setPosition(0);
+ assertTrue(reader.isSet());
+
+ reader.setPosition(1);
+ assertFalse(reader.isSet());
+ }
+ }
+
+ @Test
+ void testReaderGetMinorType() {
+ try (VariantVector vector = new VariantVector("test", allocator)) {
+ VariantReaderImpl reader = (VariantReaderImpl) vector.getReader();
+ assertEquals(vector.getMinorType(), reader.getMinorType());
+ }
+ }
+
+ @Test
+ void testReaderGetField() {
+ try (VariantVector vector = new VariantVector("test", allocator)) {
+ VariantReaderImpl reader = (VariantReaderImpl) vector.getReader();
+ assertEquals(vector.getField(), reader.getField());
+ assertEquals("test", reader.getField().getName());
+ }
+ }
+
+ @Test
+ void testReaderReadWithNonNullableVariantHolder() {
+ try (VariantVector vector = new VariantVector("test", allocator);
+ ArrowBuf metadataBuf = allocator.buffer(10);
+ ArrowBuf valueBuf = allocator.buffer(10)) {
+
+ byte[] metadata = new byte[] {1, 2, 3};
+ byte[] value = new byte[] {4, 5, 6};
+ metadataBuf.setBytes(0, metadata);
+ valueBuf.setBytes(0, value);
+
+ NullableVariantHolder holder = createNullableHolder(metadataBuf, metadata, valueBuf, value);
+
+ vector.setSafe(0, holder);
+ vector.setValueCount(1);
+
+ VariantReaderImpl reader = (VariantReaderImpl) vector.getReader();
+ reader.setPosition(0);
+
+ VariantHolder result = new VariantHolder();
+ reader.read(result);
+
+ // Verify the data was read correctly
+ byte[] actualMetadata = new byte[metadata.length];
+ byte[] actualValue = new byte[value.length];
+ result.metadataBuffer.getBytes(result.metadataStart, actualMetadata);
+ result.valueBuffer.getBytes(result.valueStart, actualValue);
+
+ assertArrayEquals(metadata, actualMetadata);
+ assertArrayEquals(value, actualValue);
+ assertEquals(1, result.isSet);
+ }
+ }
+
+ // ========== Transfer Pair Tests ==========
+
+ @Test
+ void testTransferPair() {
+ try (VariantVector fromVector = new VariantVector("from", allocator);
+ ArrowBuf metadataBuf = allocator.buffer(10);
+ ArrowBuf valueBuf = allocator.buffer(10)) {
+
+ byte[] metadata = new byte[] {1, 2, 3};
+ byte[] value = new byte[] {4, 5, 6, 7};
+ metadataBuf.setBytes(0, metadata);
+ valueBuf.setBytes(0, value);
+
+ NullableVariantHolder holder = createNullableHolder(metadataBuf, metadata, valueBuf, value);
+
+ fromVector.setSafe(0, holder);
+ fromVector.setValueCount(1);
+
+ org.apache.arrow.vector.util.TransferPair transferPair =
+ fromVector.getTransferPair(allocator);
+ VariantVector toVector = (VariantVector) transferPair.getTo();
+
+ transferPair.transfer();
+
+ assertEquals(0, fromVector.getValueCount());
+ assertEquals(1, toVector.getValueCount());
+
+ NullableVariantHolder result = new NullableVariantHolder();
+ toVector.get(0, result);
+ assertEquals(1, result.isSet);
+
+ byte[] actualMetadata = new byte[metadata.length];
+ byte[] actualValue = new byte[value.length];
+ result.metadataBuffer.getBytes(result.metadataStart, actualMetadata);
+ result.valueBuffer.getBytes(result.valueStart, actualValue);
+
+ assertArrayEquals(metadata, actualMetadata);
+ assertArrayEquals(value, actualValue);
+
+ toVector.close();
+ }
+ }
+
+ @Test
+ void testSplitAndTransfer() {
+ try (VariantVector fromVector = new VariantVector("from", allocator);
+ ArrowBuf metadataBuf1 = allocator.buffer(10);
+ ArrowBuf valueBuf1 = allocator.buffer(10);
+ ArrowBuf metadataBuf2 = allocator.buffer(10);
+ ArrowBuf valueBuf2 = allocator.buffer(10);
+ ArrowBuf metadataBuf3 = allocator.buffer(10);
+ ArrowBuf valueBuf3 = allocator.buffer(10)) {
+
+ byte[] metadata1 = new byte[] {1};
+ byte[] value1 = new byte[] {2, 3};
+ metadataBuf1.setBytes(0, metadata1);
+ valueBuf1.setBytes(0, value1);
+
+ byte[] metadata2 = new byte[] {4, 5};
+ byte[] value2 = new byte[] {6};
+ metadataBuf2.setBytes(0, metadata2);
+ valueBuf2.setBytes(0, value2);
+
+ byte[] metadata3 = new byte[] {7, 8, 9};
+ byte[] value3 = new byte[] {10, 11, 12};
+ metadataBuf3.setBytes(0, metadata3);
+ valueBuf3.setBytes(0, value3);
+
+ NullableVariantHolder holder1 =
+ createNullableHolder(metadataBuf1, metadata1, valueBuf1, value1);
+ NullableVariantHolder holder2 =
+ createNullableHolder(metadataBuf2, metadata2, valueBuf2, value2);
+ NullableVariantHolder holder3 =
+ createNullableHolder(metadataBuf3, metadata3, valueBuf3, value3);
+
+ fromVector.setSafe(0, holder1);
+ fromVector.setSafe(1, holder2);
+ fromVector.setSafe(2, holder3);
+ fromVector.setValueCount(3);
+
+ org.apache.arrow.vector.util.TransferPair transferPair =
+ fromVector.getTransferPair(allocator);
+ VariantVector toVector = (VariantVector) transferPair.getTo();
+
+ // Split and transfer indices 1-2 (middle and last)
+ transferPair.splitAndTransfer(1, 2);
+
+ assertEquals(2, toVector.getValueCount());
+
+ // Verify transferred values
+ NullableVariantHolder result1 = new NullableVariantHolder();
+ toVector.get(0, result1);
+ assertEquals(1, result1.isSet);
+
+ byte[] actualMetadata1 = new byte[metadata2.length];
+ byte[] actualValue1 = new byte[value2.length];
+ result1.metadataBuffer.getBytes(result1.metadataStart, actualMetadata1);
+ result1.valueBuffer.getBytes(result1.valueStart, actualValue1);
+ assertArrayEquals(metadata2, actualMetadata1);
+ assertArrayEquals(value2, actualValue1);
+
+ NullableVariantHolder result2 = new NullableVariantHolder();
+ toVector.get(1, result2);
+ assertEquals(1, result2.isSet);
+
+ byte[] actualMetadata2 = new byte[metadata3.length];
+ byte[] actualValue2 = new byte[value3.length];
+ result2.metadataBuffer.getBytes(result2.metadataStart, actualMetadata2);
+ result2.valueBuffer.getBytes(result2.valueStart, actualValue2);
+ assertArrayEquals(metadata3, actualMetadata2);
+ assertArrayEquals(value3, actualValue2);
+
+ toVector.close();
+ }
+ }
+
+ @Test
+ void testCopyValueSafe() {
+ try (VariantVector fromVector = new VariantVector("from", allocator);
+ VariantVector toVector = new VariantVector("to", allocator);
+ ArrowBuf metadataBuf = allocator.buffer(10);
+ ArrowBuf valueBuf = allocator.buffer(10)) {
+
+ byte[] metadata = new byte[] {1, 2};
+ byte[] value = new byte[] {3, 4, 5};
+ metadataBuf.setBytes(0, metadata);
+ valueBuf.setBytes(0, value);
+
+ NullableVariantHolder holder = createNullableHolder(metadataBuf, metadata, valueBuf, value);
+
+ fromVector.setSafe(0, holder);
+ fromVector.setValueCount(1);
+
+ org.apache.arrow.vector.util.TransferPair transferPair =
+ fromVector.makeTransferPair(toVector);
+
+ transferPair.copyValueSafe(0, 0);
+ toVector.setValueCount(1);
+
+ // Verify the value was copied
+ NullableVariantHolder result = new NullableVariantHolder();
+ toVector.get(0, result);
+ assertEquals(1, result.isSet);
+
+ byte[] actualMetadata = new byte[metadata.length];
+ byte[] actualValue = new byte[value.length];
+ result.metadataBuffer.getBytes(result.metadataStart, actualMetadata);
+ result.valueBuffer.getBytes(result.valueStart, actualValue);
+
+ assertArrayEquals(metadata, actualMetadata);
+ assertArrayEquals(value, actualValue);
+
+ // Original vector should still have the value
+ NullableVariantHolder originalResult = new NullableVariantHolder();
+ fromVector.get(0, originalResult);
+ assertEquals(1, originalResult.isSet);
+ }
+ }
+
+ @Test
+ void testGetTransferPairWithField() {
+ try (VariantVector fromVector = new VariantVector("from", allocator);
+ ArrowBuf metadataBuf = allocator.buffer(10);
+ ArrowBuf valueBuf = allocator.buffer(10)) {
+
+ byte[] metadata = new byte[] {1};
+ byte[] value = new byte[] {2};
+ metadataBuf.setBytes(0, metadata);
+ valueBuf.setBytes(0, value);
+
+ NullableVariantHolder holder = createNullableHolder(metadataBuf, metadata, valueBuf, value);
+
+ fromVector.setSafe(0, holder);
+ fromVector.setValueCount(1);
+
+ org.apache.arrow.vector.util.TransferPair transferPair =
+ fromVector.getTransferPair(fromVector.getField(), allocator);
+ VariantVector toVector = (VariantVector) transferPair.getTo();
+
+ transferPair.transfer();
+
+ assertEquals(1, toVector.getValueCount());
+ assertEquals(fromVector.getField().getName(), toVector.getField().getName());
+
+ toVector.close();
+ }
+ }
+
+ // ========== Copy Operations Tests ==========
+
+ @Test
+ void testCopyFrom() {
+ try (VariantVector fromVector = new VariantVector("from", allocator);
+ VariantVector toVector = new VariantVector("to", allocator);
+ ArrowBuf metadataBuf = allocator.buffer(10);
+ ArrowBuf valueBuf = allocator.buffer(10)) {
+
+ byte[] metadata = new byte[] {1, 2, 3};
+ byte[] value = new byte[] {4, 5};
+ metadataBuf.setBytes(0, metadata);
+ valueBuf.setBytes(0, value);
+
+ NullableVariantHolder holder = createNullableHolder(metadataBuf, metadata, valueBuf, value);
+
+ fromVector.setSafe(0, holder);
+ fromVector.setValueCount(1);
+
+ toVector.allocateNew();
+ toVector.copyFrom(0, 0, fromVector);
+ toVector.setValueCount(1);
+
+ NullableVariantHolder result = new NullableVariantHolder();
+ toVector.get(0, result);
+ assertEquals(1, result.isSet);
+
+ byte[] actualMetadata = new byte[metadata.length];
+ byte[] actualValue = new byte[value.length];
+ result.metadataBuffer.getBytes(result.metadataStart, actualMetadata);
+ result.valueBuffer.getBytes(result.valueStart, actualValue);
+
+ assertArrayEquals(metadata, actualMetadata);
+ assertArrayEquals(value, actualValue);
+ }
+ }
+
+ @Test
+ void testCopyFromSafe() {
+ try (VariantVector fromVector = new VariantVector("from", allocator);
+ VariantVector toVector = new VariantVector("to", allocator);
+ ArrowBuf metadataBuf1 = allocator.buffer(10);
+ ArrowBuf valueBuf1 = allocator.buffer(10);
+ ArrowBuf metadataBuf2 = allocator.buffer(10);
+ ArrowBuf valueBuf2 = allocator.buffer(10)) {
+
+ byte[] metadata1 = new byte[] {1};
+ byte[] value1 = new byte[] {2, 3};
+ metadataBuf1.setBytes(0, metadata1);
+ valueBuf1.setBytes(0, value1);
+
+ NullableVariantHolder holder1 =
+ createNullableHolder(metadataBuf1, metadata1, valueBuf1, value1);
+
+ byte[] metadata2 = new byte[] {4, 5};
+ byte[] value2 = new byte[] {6};
+ metadataBuf2.setBytes(0, metadata2);
+ valueBuf2.setBytes(0, value2);
+
+ NullableVariantHolder holder2 =
+ createNullableHolder(metadataBuf2, metadata2, valueBuf2, value2);
+
+ fromVector.setSafe(0, holder1);
+ fromVector.setSafe(1, holder2);
+ fromVector.setValueCount(2);
+
+ // Copy without pre-allocating toVector
+ for (int i = 0; i < 2; i++) {
+ toVector.copyFromSafe(i, i, fromVector);
+ }
+ toVector.setValueCount(2);
+
+ // Verify both values
+ NullableVariantHolder result1 = new NullableVariantHolder();
+ toVector.get(0, result1);
+ assertEquals(1, result1.isSet);
+
+ byte[] actualMetadata1 = new byte[metadata1.length];
+ byte[] actualValue1 = new byte[value1.length];
+ result1.metadataBuffer.getBytes(result1.metadataStart, actualMetadata1);
+ result1.valueBuffer.getBytes(result1.valueStart, actualValue1);
+ assertArrayEquals(metadata1, actualMetadata1);
+ assertArrayEquals(value1, actualValue1);
+
+ NullableVariantHolder result2 = new NullableVariantHolder();
+ toVector.get(1, result2);
+ assertEquals(1, result2.isSet);
+
+ byte[] actualMetadata2 = new byte[metadata2.length];
+ byte[] actualValue2 = new byte[value2.length];
+ result2.metadataBuffer.getBytes(result2.metadataStart, actualMetadata2);
+ result2.valueBuffer.getBytes(result2.valueStart, actualValue2);
+ assertArrayEquals(metadata2, actualMetadata2);
+ assertArrayEquals(value2, actualValue2);
+ }
+ }
+
+ @Test
+ void testCopyFromWithNulls() {
+ try (VariantVector fromVector = new VariantVector("from", allocator);
+ VariantVector toVector = new VariantVector("to", allocator);
+ ArrowBuf metadataBuf = allocator.buffer(10);
+ ArrowBuf valueBuf = allocator.buffer(10)) {
+
+ byte[] metadata = new byte[] {1};
+ byte[] value = new byte[] {2};
+ metadataBuf.setBytes(0, metadata);
+ valueBuf.setBytes(0, value);
+
+ NullableVariantHolder holder = createNullableHolder(metadataBuf, metadata, valueBuf, value);
+
+ fromVector.setSafe(0, holder);
+ fromVector.setNull(1);
+ fromVector.setSafe(2, holder);
+ fromVector.setValueCount(3);
+
+ toVector.allocateNew();
+ for (int i = 0; i < 3; i++) {
+ toVector.copyFromSafe(i, i, fromVector);
+ }
+ toVector.setValueCount(3);
+
+ assertFalse(toVector.isNull(0));
+ assertTrue(toVector.isNull(1));
+ assertFalse(toVector.isNull(2));
+ }
+ }
+
+ // ========== GetObject Tests ==========
+
+ @Test
+ void testGetObject() {
+ try (VariantVector vector = new VariantVector("test", allocator);
+ ArrowBuf metadataBuf = allocator.buffer(10);
+ ArrowBuf valueBuf = allocator.buffer(10)) {
+
+ byte[] metadata = new byte[] {1, 2};
+ byte[] value = new byte[] {3, 4, 5};
+ metadataBuf.setBytes(0, metadata);
+ valueBuf.setBytes(0, value);
+
+ NullableVariantHolder holder = createNullableHolder(metadataBuf, metadata, valueBuf, value);
+
+ vector.setSafe(0, holder);
+ vector.setValueCount(1);
+
+ Object obj = vector.getObject(0);
+ assertNotNull(obj);
+ assertTrue(obj instanceof Variant);
+ assertEquals(new Variant(metadata, value), obj);
+ }
+ }
+
+ @Test
+ void testGetObjectNull() {
+ try (VariantVector vector = new VariantVector("test", allocator)) {
+ vector.setNull(0);
+ vector.setValueCount(1);
+
+ Object obj = vector.getObject(0);
+ assertNull(obj);
+ }
+ }
+
+ // ========== Allocate and Capacity Tests ==========
+
+ @Test
+ void testAllocateNew() {
+ try (VariantVector vector = new VariantVector("test", allocator)) {
+ vector.allocateNew();
+ assertTrue(vector.getValueCapacity() > 0);
+ }
+ }
+
+ @Test
+ void testSetInitialCapacity() {
+ try (VariantVector vector = new VariantVector("test", allocator)) {
+ vector.setInitialCapacity(100);
+ vector.allocateNew();
+ assertTrue(vector.getValueCapacity() >= 100);
+ }
+ }
+
+ @Test
+ void testClearAndReuse() {
+ try (VariantVector vector = new VariantVector("test", allocator);
+ ArrowBuf metadataBuf = allocator.buffer(10);
+ ArrowBuf valueBuf = allocator.buffer(10)) {
+
+ byte[] metadata = new byte[] {1};
+ byte[] value = new byte[] {2};
+ metadataBuf.setBytes(0, metadata);
+ valueBuf.setBytes(0, value);
+
+ NullableVariantHolder holder = createNullableHolder(metadataBuf, metadata, valueBuf, value);
+
+ vector.setSafe(0, holder);
+ vector.setValueCount(1);
+
+ assertFalse(vector.isNull(0));
+
+ vector.clear();
+ vector.allocateNew();
+
+ // After clear, vector should be empty
+ assertEquals(0, vector.getValueCount());
+ }
+ }
+}
diff --git a/bom/pom.xml b/bom/pom.xml
index 171d0bc5e9..2d1085b160 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -23,13 +23,13 @@ under the License.
org.apacheapache
- 33
-
+ 38
+ org.apache.arrowarrow-bom
- 19.0.0-SNAPSHOT
+ 20.0.0-SNAPSHOTpomArrow Bill of Materials
@@ -78,12 +78,12 @@ under the License.
-
+
- 11
- 11
- 11
- 11
+ 17
+ 17
+ 17
+ 17
@@ -165,7 +165,7 @@ under the License.
${project.version}
- org.apache.arrow
+ org.apache.arrow.gandivaarrow-gandiva${project.version}
@@ -194,6 +194,11 @@ under the License.
arrow-tools${project.version}
+
+ org.apache.arrow
+ arrow-variant
+ ${project.version}
+
@@ -203,12 +208,12 @@ under the License.
com.diffplug.spotlessspotless-maven-plugin
- 2.44.3
+ 3.8.0org.codehaus.mojoversions-maven-plugin
- 2.18.0
+ 2.21.0
@@ -230,7 +235,7 @@ under the License.
${maven.multiModuleProjectDirectory}/dev/license/asf-xml.license(<configuration|<project)
-
+
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.arrowarrow-java-root
- 19.0.0-SNAPSHOT
+ 20.0.0-SNAPSHOTarrow-c-data
diff --git a/c/src/main/cpp/jni_wrapper.cc b/c/src/main/cpp/jni_wrapper.cc
index 35c2b7787e..3d7a194563 100644
--- a/c/src/main/cpp/jni_wrapper.cc
+++ b/c/src/main/cpp/jni_wrapper.cc
@@ -205,8 +205,9 @@ void TryCopyLastError(JNIEnv* env, InnerPrivateData* private_data) {
return;
}
+ jsize error_bytes_len = env->GetArrayLength(arr);
char* error_str = reinterpret_cast(error_bytes);
- private_data->last_error_ = std::string(error_str, std::strlen(error_str));
+ private_data->last_error_ = std::string(error_str, error_bytes_len);
env->ReleaseByteArrayElements(arr, error_bytes, JNI_ABORT);
}
@@ -326,19 +327,20 @@ void ArrowArrayStreamRelease(ArrowArrayStream* stream) {
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
- if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) {
- return JNI_ERR;
+ const int err_code = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION);
+ if (err_code != JNI_OK) {
+ return err_code;
}
JNI_METHOD_START
- kObjectClass = CreateGlobalClassReference(env, "Ljava/lang/Object;");
+ kObjectClass = CreateGlobalClassReference(env, "java/lang/Object");
kRuntimeExceptionClass =
- CreateGlobalClassReference(env, "Ljava/lang/RuntimeException;");
+ CreateGlobalClassReference(env, "java/lang/RuntimeException");
kPrivateDataClass =
- CreateGlobalClassReference(env, "Lorg/apache/arrow/c/jni/PrivateData;");
+ CreateGlobalClassReference(env, "org/apache/arrow/c/jni/PrivateData");
kCDataExceptionClass =
- CreateGlobalClassReference(env, "Lorg/apache/arrow/c/jni/CDataJniException;");
+ CreateGlobalClassReference(env, "org/apache/arrow/c/jni/CDataJniException");
kStreamPrivateDataClass = CreateGlobalClassReference(
- env, "Lorg/apache/arrow/c/ArrayStreamExporter$ExportedArrayStreamPrivateData;");
+ env, "org/apache/arrow/c/ArrayStreamExporter$ExportedArrayStreamPrivateData");
kPrivateDataLastErrorField =
GetFieldID(env, kStreamPrivateDataClass, "lastError", "[B");
diff --git a/c/src/main/java/org/apache/arrow/c/ArrayImporter.java b/c/src/main/java/org/apache/arrow/c/ArrayImporter.java
index b74fb1b473..f31a8a1faa 100644
--- a/c/src/main/java/org/apache/arrow/c/ArrayImporter.java
+++ b/c/src/main/java/org/apache/arrow/c/ArrayImporter.java
@@ -58,7 +58,6 @@ void importArray(ArrowArray src) {
ArrowArray ownedArray = ArrowArray.allocateNew(allocator);
ownedArray.save(snapshot);
src.markReleased();
- src.close();
recursionLevel = 0;
diff --git a/c/src/main/java/org/apache/arrow/c/ArrowArrayStreamReader.java b/c/src/main/java/org/apache/arrow/c/ArrowArrayStreamReader.java
index 07a88cd8d7..34a9c4ec03 100644
--- a/c/src/main/java/org/apache/arrow/c/ArrowArrayStreamReader.java
+++ b/c/src/main/java/org/apache/arrow/c/ArrowArrayStreamReader.java
@@ -44,7 +44,6 @@ final class ArrowArrayStreamReader extends ArrowReader {
this.ownedStream = ArrowArrayStream.allocateNew(allocator);
this.ownedStream.save(snapshot);
stream.markReleased();
- stream.close();
}
@Override
diff --git a/c/src/main/java/org/apache/arrow/c/Data.java b/c/src/main/java/org/apache/arrow/c/Data.java
index 0b4da33b4e..f9d2ee4542 100644
--- a/c/src/main/java/org/apache/arrow/c/Data.java
+++ b/c/src/main/java/org/apache/arrow/c/Data.java
@@ -231,6 +231,22 @@ public static void exportArrayStream(
new ArrayStreamExporter(allocator).export(out, reader);
}
+ /**
+ * Equivalent to calling {@link #importField(BufferAllocator, ArrowSchema,
+ * CDataDictionaryProvider, boolean) importField(allocator, schema, provider, true)}.
+ *
+ * @param allocator Buffer allocator for allocating dictionary vectors
+ * @param schema C data interface struct representing the field [inout]
+ * @param provider A dictionary provider will be initialized with empty dictionary vectors
+ * (optional)
+ * @return Imported field object
+ * @see #importField(BufferAllocator, ArrowSchema, CDataDictionaryProvider, boolean)
+ */
+ public static Field importField(
+ BufferAllocator allocator, ArrowSchema schema, CDataDictionaryProvider provider) {
+ return importField(allocator, schema, provider, true);
+ }
+
/**
* Import Java Field from the C data interface.
*
@@ -241,19 +257,42 @@ public static void exportArrayStream(
* @param schema C data interface struct representing the field [inout]
* @param provider A dictionary provider will be initialized with empty dictionary vectors
* (optional)
+ * @param closeImportedStructs if true, the ArrowSchema struct will be closed when this method
+ * completes.
* @return Imported field object
*/
public static Field importField(
- BufferAllocator allocator, ArrowSchema schema, CDataDictionaryProvider provider) {
+ BufferAllocator allocator,
+ ArrowSchema schema,
+ CDataDictionaryProvider provider,
+ boolean closeImportedStructs) {
try {
SchemaImporter importer = new SchemaImporter(allocator);
return importer.importField(schema, provider);
} finally {
schema.release();
- schema.close();
+ if (closeImportedStructs) {
+ schema.close();
+ }
}
}
+ /**
+ * Equivalent to calling {@link #importSchema(BufferAllocator, ArrowSchema,
+ * CDataDictionaryProvider, boolean) importSchema(allocator, schema, provider, true)}.
+ *
+ * @param allocator Buffer allocator for allocating dictionary vectors
+ * @param schema C data interface struct representing the field
+ * @param provider A dictionary provider will be initialized with empty dictionary vectors
+ * (optional)
+ * @return Imported schema object
+ * @see #importSchema(BufferAllocator, ArrowSchema, CDataDictionaryProvider, boolean)
+ */
+ public static Schema importSchema(
+ BufferAllocator allocator, ArrowSchema schema, CDataDictionaryProvider provider) {
+ return importSchema(allocator, schema, provider, true);
+ }
+
/**
* Import Java Schema from the C data interface.
*
@@ -264,11 +303,16 @@ public static Field importField(
* @param schema C data interface struct representing the field
* @param provider A dictionary provider will be initialized with empty dictionary vectors
* (optional)
+ * @param closeImportedStructs if true, the ArrowSchema struct will be closed when this method
+ * completes.
* @return Imported schema object
*/
public static Schema importSchema(
- BufferAllocator allocator, ArrowSchema schema, CDataDictionaryProvider provider) {
- Field structField = importField(allocator, schema, provider);
+ BufferAllocator allocator,
+ ArrowSchema schema,
+ CDataDictionaryProvider provider,
+ boolean closeImportedStructs) {
+ Field structField = importField(allocator, schema, provider, closeImportedStructs);
if (structField.getType().getTypeID() != ArrowTypeID.Struct) {
throw new IllegalArgumentException(
"Cannot import schema: ArrowSchema describes non-struct type");
@@ -276,24 +320,67 @@ public static Schema importSchema(
return new Schema(structField.getChildren(), structField.getMetadata());
}
+ /**
+ * Equivalent to calling {@link #importIntoVector(BufferAllocator, ArrowArray, FieldVector,
+ * DictionaryProvider, boolean)} importIntoVector(allocator, array, vector, provider, true)}.
+ *
+ * @param allocator Buffer allocator
+ * @param array C data interface struct holding the array data
+ * @param vector Imported vector object [out]
+ * @param provider Dictionary provider to load dictionary vectors to (optional)
+ * @see #importIntoVector(BufferAllocator, ArrowArray, FieldVector, DictionaryProvider, boolean)
+ */
+ public static void importIntoVector(
+ BufferAllocator allocator,
+ ArrowArray array,
+ FieldVector vector,
+ DictionaryProvider provider) {
+ importIntoVector(allocator, array, vector, provider, true);
+ }
+
/**
* Import Java vector from the C data interface.
*
- *
The ArrowArray struct has its contents moved (as per the C data interface specification) to
- * a private object held alive by the resulting array.
+ *
On successful completion, the ArrowArray struct will have been moved (as per the C data
+ * interface specification) to a private object held alive by the resulting array.
*
* @param allocator Buffer allocator
* @param array C data interface struct holding the array data
* @param vector Imported vector object [out]
* @param provider Dictionary provider to load dictionary vectors to (optional)
+ * @param closeImportedStructs if true, the ArrowArray struct will be closed when this method
+ * completes successfully.
*/
public static void importIntoVector(
BufferAllocator allocator,
ArrowArray array,
FieldVector vector,
- DictionaryProvider provider) {
+ DictionaryProvider provider,
+ boolean closeImportedStructs) {
ArrayImporter importer = new ArrayImporter(allocator, vector, provider);
importer.importArray(array);
+ if (closeImportedStructs) {
+ array.close();
+ }
+ }
+
+ /**
+ * Equivalent to calling {@link #importVector(BufferAllocator, ArrowArray, ArrowSchema,
+ * CDataDictionaryProvider, boolean) importVector(allocator, array, schema, provider, true)}.
+ *
+ * @param allocator Buffer allocator for allocating the output FieldVector
+ * @param array C data interface struct holding the array data
+ * @param schema C data interface struct holding the array type
+ * @param provider Dictionary provider to load dictionary vectors to (optional)
+ * @return Imported vector object
+ * @see #importVector(BufferAllocator, ArrowArray, ArrowSchema, CDataDictionaryProvider, boolean)
+ */
+ public static FieldVector importVector(
+ BufferAllocator allocator,
+ ArrowArray array,
+ ArrowSchema schema,
+ CDataDictionaryProvider provider) {
+ return importVector(allocator, array, schema, provider, true);
}
/**
@@ -307,19 +394,42 @@ public static void importIntoVector(
* @param array C data interface struct holding the array data
* @param schema C data interface struct holding the array type
* @param provider Dictionary provider to load dictionary vectors to (optional)
+ * @param closeImportedStructs if true, the ArrowArray struct will be closed when this method
+ * completes successfully and the ArrowSchema struct will be always be closed.
* @return Imported vector object
*/
public static FieldVector importVector(
BufferAllocator allocator,
ArrowArray array,
ArrowSchema schema,
- CDataDictionaryProvider provider) {
- Field field = importField(allocator, schema, provider);
+ CDataDictionaryProvider provider,
+ boolean closeImportedStructs) {
+ Field field = importField(allocator, schema, provider, closeImportedStructs);
FieldVector vector = field.createVector(allocator);
- importIntoVector(allocator, array, vector, provider);
+ importIntoVector(allocator, array, vector, provider, closeImportedStructs);
return vector;
}
+ /**
+ * Equivalent to calling {@link #importIntoVectorSchemaRoot(BufferAllocator, ArrowArray,
+ * VectorSchemaRoot, DictionaryProvider, boolean) importIntoVectorSchemaRoot(allocator, array,
+ * root, provider, true)}.
+ *
+ * @param allocator Buffer allocator
+ * @param array C data interface struct holding the record batch data
+ * @param root vector schema root to load into
+ * @param provider Dictionary provider to load dictionary vectors to (optional)
+ * @see #importIntoVectorSchemaRoot(BufferAllocator, ArrowArray, VectorSchemaRoot,
+ * DictionaryProvider, boolean)
+ */
+ public static void importIntoVectorSchemaRoot(
+ BufferAllocator allocator,
+ ArrowArray array,
+ VectorSchemaRoot root,
+ DictionaryProvider provider) {
+ importIntoVectorSchemaRoot(allocator, array, root, provider, true);
+ }
+
/**
* Import record batch from the C data interface into vector schema root.
*
@@ -333,15 +443,18 @@ public static FieldVector importVector(
* @param array C data interface struct holding the record batch data
* @param root vector schema root to load into
* @param provider Dictionary provider to load dictionary vectors to (optional)
+ * @param closeImportedStructs if true, the ArrowArray struct will be closed when this method
+ * completes successfully
*/
public static void importIntoVectorSchemaRoot(
BufferAllocator allocator,
ArrowArray array,
VectorSchemaRoot root,
- DictionaryProvider provider) {
+ DictionaryProvider provider,
+ boolean closeImportedStructs) {
try (StructVector structVector = StructVector.emptyWithDuplicates("", allocator)) {
structVector.initializeChildrenFromFields(root.getSchema().getFields());
- importIntoVector(allocator, array, structVector, provider);
+ importIntoVector(allocator, array, structVector, provider, closeImportedStructs);
StructVectorUnloader unloader = new StructVectorUnloader(structVector);
VectorLoader loader = new VectorLoader(root);
try (ArrowRecordBatch recordBatch = unloader.getRecordBatch()) {
@@ -350,6 +463,21 @@ public static void importIntoVectorSchemaRoot(
}
}
+ /**
+ * Equivalent to calling {@link #importVectorSchemaRoot(BufferAllocator, ArrowSchema,
+ * CDataDictionaryProvider, boolean) importVectorSchemaRoot(allocator, schema, provider, true)}.
+ *
+ * @param allocator Buffer allocator for allocating the output VectorSchemaRoot
+ * @param schema C data interface struct holding the record batch schema
+ * @param provider Dictionary provider to load dictionary vectors to (optional)
+ * @return Imported vector schema root
+ * @see #importVectorSchemaRoot(BufferAllocator, ArrowSchema, CDataDictionaryProvider, boolean)
+ */
+ public static VectorSchemaRoot importVectorSchemaRoot(
+ BufferAllocator allocator, ArrowSchema schema, CDataDictionaryProvider provider) {
+ return importVectorSchemaRoot(allocator, schema, provider, true);
+ }
+
/**
* Import Java vector schema root from a C data interface Schema.
*
@@ -360,11 +488,37 @@ public static void importIntoVectorSchemaRoot(
* @param allocator Buffer allocator for allocating the output VectorSchemaRoot
* @param schema C data interface struct holding the record batch schema
* @param provider Dictionary provider to load dictionary vectors to (optional)
+ * @param closeImportedStructs if true, the ArrowSchema struct will be closed when this method
+ * completes
* @return Imported vector schema root
*/
public static VectorSchemaRoot importVectorSchemaRoot(
- BufferAllocator allocator, ArrowSchema schema, CDataDictionaryProvider provider) {
- return importVectorSchemaRoot(allocator, null, schema, provider);
+ BufferAllocator allocator,
+ ArrowSchema schema,
+ CDataDictionaryProvider provider,
+ boolean closeImportedStructs) {
+ return importVectorSchemaRoot(allocator, null, schema, provider, closeImportedStructs);
+ }
+
+ /**
+ * Equivalent to calling {@link #importVectorSchemaRoot(BufferAllocator, ArrowArray, ArrowSchema,
+ * CDataDictionaryProvider, boolean) importVectorSchemaRoot(allocator, array, schema, provider,
+ * true)}.
+ *
+ * @param allocator Buffer allocator for allocating the output VectorSchemaRoot
+ * @param array C data interface struct holding the record batch data (optional)
+ * @param schema C data interface struct holding the record batch schema
+ * @param provider Dictionary provider to load dictionary vectors to (optional)
+ * @return Imported vector schema root
+ * @see #importVectorSchemaRoot(BufferAllocator, ArrowArray, ArrowSchema, CDataDictionaryProvider,
+ * boolean)
+ */
+ public static VectorSchemaRoot importVectorSchemaRoot(
+ BufferAllocator allocator,
+ ArrowArray array,
+ ArrowSchema schema,
+ CDataDictionaryProvider provider) {
+ return importVectorSchemaRoot(allocator, array, schema, provider, true);
}
/**
@@ -383,29 +537,56 @@ public static VectorSchemaRoot importVectorSchemaRoot(
* @param array C data interface struct holding the record batch data (optional)
* @param schema C data interface struct holding the record batch schema
* @param provider Dictionary provider to load dictionary vectors to (optional)
+ * @param closeImportedStructs if true, the ArrowArray struct will be closed when this method
+ * completes successfully and the ArrowSchema struct will be always be closed.
* @return Imported vector schema root
*/
public static VectorSchemaRoot importVectorSchemaRoot(
BufferAllocator allocator,
ArrowArray array,
ArrowSchema schema,
- CDataDictionaryProvider provider) {
+ CDataDictionaryProvider provider,
+ boolean closeImportedStructs) {
VectorSchemaRoot vsr =
- VectorSchemaRoot.create(importSchema(allocator, schema, provider), allocator);
+ VectorSchemaRoot.create(
+ importSchema(allocator, schema, provider, closeImportedStructs), allocator);
if (array != null) {
- importIntoVectorSchemaRoot(allocator, array, vsr, provider);
+ importIntoVectorSchemaRoot(allocator, array, vsr, provider, closeImportedStructs);
}
return vsr;
}
/**
- * Import an ArrowArrayStream as an {@link ArrowReader}.
+ * Equivalent to calling {@link #importArrayStream(BufferAllocator, ArrowArrayStream, boolean)
+ * importArrayStream(allocator, stream, true)}.
*
* @param allocator Buffer allocator for allocating the output data.
* @param stream C stream interface struct to import.
* @return Imported reader
+ * @see #importArrayStream(BufferAllocator, ArrowArrayStream, boolean)
*/
public static ArrowReader importArrayStream(BufferAllocator allocator, ArrowArrayStream stream) {
- return new ArrowArrayStreamReader(allocator, stream);
+ return importArrayStream(allocator, stream, true);
+ }
+
+ /**
+ * Import an ArrowArrayStream as an {@link ArrowReader}.
+ *
+ *
On successful completion, the ArrowArrayStream struct will have been moved (as per the C
+ * data interface specification) to a private object held alive by the resulting ArrowReader.
+ *
+ * @param allocator Buffer allocator for allocating the output data.
+ * @param stream C stream interface struct to import.
+ * @param closeImportedStructs if true, the ArrowArrayStream struct will be closed when this
+ * method completes successfully
+ * @return Imported reader
+ */
+ public static ArrowReader importArrayStream(
+ BufferAllocator allocator, ArrowArrayStream stream, boolean closeImportedStructs) {
+ ArrowArrayStreamReader reader = new ArrowArrayStreamReader(allocator, stream);
+ if (closeImportedStructs) {
+ stream.close();
+ }
+ return reader;
}
}
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/main/java/org/apache/arrow/c/jni/JniLoader.java b/c/src/main/java/org/apache/arrow/c/jni/JniLoader.java
index f712b400bf..46c93f5541 100644
--- a/c/src/main/java/org/apache/arrow/c/jni/JniLoader.java
+++ b/c/src/main/java/org/apache/arrow/c/jni/JniLoader.java
@@ -75,8 +75,23 @@ private synchronized void loadRemaining() {
}
private void load(String name) {
- final String libraryToLoad =
- name + "/" + getNormalizedArch() + "/" + System.mapLibraryName(name);
+ String libraryName = System.mapLibraryName(name);
+
+ // If 'arrow.cdata.library.path' is defined, try to load the native library from there
+ String libraryPath = System.getProperty("arrow.cdata.library.path");
+ if (libraryPath != null) {
+ try {
+ File libraryFile = new File(libraryPath, libraryName);
+ if (libraryFile.isFile()) {
+ System.load(libraryFile.getAbsolutePath());
+ return;
+ }
+ } catch (UnsatisfiedLinkError e) {
+ // Ignore this error and fall back to extracting from the JAR file
+ }
+ }
+
+ final String libraryToLoad = name + "/" + getNormalizedArch() + "/" + libraryName;
try {
File temp =
File.createTempFile("jnilib-", ".tmp", new File(System.getProperty("java.io.tmpdir")));
diff --git a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java
new file mode 100644
index 0000000000..5bc96a8f99
--- /dev/null
+++ b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowableOfType;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.vector.VectorLoader;
+import org.apache.arrow.vector.VectorSchemaRoot;
+import org.apache.arrow.vector.dictionary.Dictionary;
+import org.apache.arrow.vector.dictionary.DictionaryProvider;
+import org.apache.arrow.vector.ipc.ArrowReader;
+import org.apache.arrow.vector.ipc.message.ArrowRecordBatch;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.Field;
+import org.apache.arrow.vector.types.pojo.Schema;
+import org.junit.jupiter.api.Test;
+
+// Regression test for https://github.com/apache/arrow-java/issues/759
+final class ExceptionTest {
+ @Test
+ public void testException() throws IOException {
+ final Schema schema =
+ new Schema(Collections.singletonList(Field.nullable("ints", new ArrowType.Int(32, true))));
+ final List
+
diff --git a/vector/src/main/codegen/includes/vv_imports.ftl b/vector/src/main/codegen/includes/vv_imports.ftl
index 7f216a7b43..2bbcecc856 100644
--- a/vector/src/main/codegen/includes/vv_imports.ftl
+++ b/vector/src/main/codegen/includes/vv_imports.ftl
@@ -34,6 +34,7 @@ import org.apache.arrow.vector.complex.*;
import org.apache.arrow.vector.complex.reader.*;
import org.apache.arrow.vector.complex.impl.*;
import org.apache.arrow.vector.complex.writer.*;
+import org.apache.arrow.vector.complex.writer.BaseWriter.ExtensionWriter;
import org.apache.arrow.vector.complex.writer.BaseWriter.StructWriter;
import org.apache.arrow.vector.complex.writer.BaseWriter.ListWriter;
import org.apache.arrow.vector.complex.writer.BaseWriter.MapWriter;
diff --git a/vector/src/main/codegen/templates/AbstractFieldReader.java b/vector/src/main/codegen/templates/AbstractFieldReader.java
index 25b071fab7..789295e959 100644
--- a/vector/src/main/codegen/templates/AbstractFieldReader.java
+++ b/vector/src/main/codegen/templates/AbstractFieldReader.java
@@ -29,9 +29,9 @@
* Source code generated using FreeMarker template ${.template_name}
*/
@SuppressWarnings("unused")
-abstract class AbstractFieldReader extends AbstractBaseReader implements FieldReader{
+public abstract class AbstractFieldReader extends AbstractBaseReader implements FieldReader{
- AbstractFieldReader(){
+ protected AbstractFieldReader(){
super();
}
@@ -108,6 +108,23 @@ public void copyAsField(String name, ${name}Writer writer) {
}
#list>#list>
+
+ public void read(ExtensionHolder holder) {
+ fail("Extension");
+ }
+
+ public void read(int arrayIndex, ExtensionHolder holder) {
+ fail("RepeatedExtension");
+ }
+
+ public void copyAsValue(AbstractExtensionTypeWriter writer) {
+ fail("CopyAsValueExtension");
+ }
+
+ public void copyAsField(String name, AbstractExtensionTypeWriter writer) {
+ fail("CopyAsFieldExtension");
+ }
+
public FieldReader reader(String name) {
fail("reader(String name)");
return null;
@@ -126,4 +143,5 @@ public int size() {
private void fail(String name) {
throw new IllegalArgumentException(String.format("You tried to read a [%s] type when you are using a field reader of type [%s].", name, this.getClass().getSimpleName()));
}
+
}
diff --git a/vector/src/main/codegen/templates/AbstractFieldWriter.java b/vector/src/main/codegen/templates/AbstractFieldWriter.java
index ae5b97faef..4b4a17d932 100644
--- a/vector/src/main/codegen/templates/AbstractFieldWriter.java
+++ b/vector/src/main/codegen/templates/AbstractFieldWriter.java
@@ -107,14 +107,17 @@ public void endEntry() {
throw new IllegalStateException(String.format("You tried to end a map entry when you are using a ValueWriter of type %s.", this.getClass().getSimpleName()));
}
+ @Override
public void write(ExtensionHolder var1) {
- this.fail("ExtensionType");
+ this.fail("Cannot write ExtensionHolder");
}
+ @Override
public void writeExtension(Object var1) {
- this.fail("ExtensionType");
+ this.fail("Cannot write extension object");
}
- public void addExtensionTypeWriterFactory(ExtensionTypeWriterFactory var1) {
- this.fail("ExtensionType");
+ @Override
+ public void writeExtension(Object var1, ArrowType type) {
+ this.fail("Cannot write extension with type " + type);
}
<#list vv.types as type><#list type.minor as minor><#assign name = minor.class?cap_first />
diff --git a/vector/src/main/codegen/templates/AbstractPromotableFieldWriter.java b/vector/src/main/codegen/templates/AbstractPromotableFieldWriter.java
index 951edd5eee..2e7792fcfe 100644
--- a/vector/src/main/codegen/templates/AbstractPromotableFieldWriter.java
+++ b/vector/src/main/codegen/templates/AbstractPromotableFieldWriter.java
@@ -295,7 +295,7 @@ public MapWriter map(boolean keysSorted) {
@Override
public ExtensionWriter extension(ArrowType arrowType) {
- return getWriter(MinorType.EXTENSIONTYPE).extension(arrowType);
+ return getWriter(MinorType.LIST).extension(arrowType);
}
@Override
@@ -325,7 +325,7 @@ public MapWriter map(String name, boolean keysSorted) {
@Override
public ExtensionWriter extension(String name, ArrowType arrowType) {
- return getWriter(MinorType.EXTENSIONTYPE).extension(name, arrowType);
+ return getWriter(MinorType.STRUCT).extension(name, arrowType);
}
<#list vv.types as type><#list type.minor as minor>
diff --git a/vector/src/main/codegen/templates/ArrowType.java b/vector/src/main/codegen/templates/ArrowType.java
index fd35c1cd2b..b428f09155 100644
--- a/vector/src/main/codegen/templates/ArrowType.java
+++ b/vector/src/main/codegen/templates/ArrowType.java
@@ -27,8 +27,10 @@
import org.apache.arrow.flatbuf.Type;
import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.vector.complex.writer.FieldWriter;
import org.apache.arrow.vector.types.*;
import org.apache.arrow.vector.FieldVector;
+import org.apache.arrow.vector.ValueVector;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -331,6 +333,10 @@ public boolean equals(Object obj) {
public T accept(ArrowTypeVisitor visitor) {
return visitor.visit(this);
}
+
+ public FieldWriter getNewFieldWriter(ValueVector vector) {
+ throw new UnsupportedOperationException("WriterImpl not yet implemented.");
+ }
}
private static final int defaultDecimalBitWidth = 128;
diff --git a/vector/src/main/codegen/templates/BaseReader.java b/vector/src/main/codegen/templates/BaseReader.java
index e75e8a2974..c52345af21 100644
--- a/vector/src/main/codegen/templates/BaseReader.java
+++ b/vector/src/main/codegen/templates/BaseReader.java
@@ -73,7 +73,7 @@ public interface RepeatedMapReader extends MapReader{
public interface ScalarReader extends
<#list vv.types as type><#list type.minor as minor><#assign name = minor.class?cap_first /> ${name}Reader, #list>#list>
- BaseReader {}
+ ExtensionReader, BaseReader {}
interface ComplexReader{
StructReader rootAsStruct();
diff --git a/vector/src/main/codegen/templates/BaseWriter.java b/vector/src/main/codegen/templates/BaseWriter.java
index 78da7fddc3..a4c98d7089 100644
--- a/vector/src/main/codegen/templates/BaseWriter.java
+++ b/vector/src/main/codegen/templates/BaseWriter.java
@@ -125,11 +125,12 @@ public interface ExtensionWriter extends BaseWriter {
void writeExtension(Object value);
/**
- * Adds the given extension type factory. This factory allows configuring writer implementations for specific ExtensionTypeVector.
+ * Writes the given extension type value.
*
- * @param factory the extension type factory to add
+ * @param value the extension type value to write
+ * @param type of the extension
*/
- void addExtensionTypeWriterFactory(ExtensionTypeWriterFactory factory);
+ void writeExtension(Object value, ArrowType type);
}
public interface ScalarWriter extends
diff --git a/vector/src/main/codegen/templates/ComplexCopier.java b/vector/src/main/codegen/templates/ComplexCopier.java
index 4fff7059a7..6655f6c2a7 100644
--- a/vector/src/main/codegen/templates/ComplexCopier.java
+++ b/vector/src/main/codegen/templates/ComplexCopier.java
@@ -41,11 +41,8 @@ public class ComplexCopier {
* @param input field to read from
* @param output field to write to
*/
- public static void copy(FieldReader input, FieldWriter output) {
- writeValue(input, output);
- }
+ public static void copy(FieldReader reader, FieldWriter writer) {
- private static void writeValue(FieldReader reader, FieldWriter writer) {
final MinorType mt = reader.getMinorType();
switch (mt) {
@@ -61,7 +58,7 @@ private static void writeValue(FieldReader reader, FieldWriter writer) {
FieldReader childReader = reader.reader();
FieldWriter childWriter = getListWriterForReader(childReader, writer);
if (childReader.isSet()) {
- writeValue(childReader, childWriter);
+ copy(childReader, childWriter);
} else {
childWriter.writeNull();
}
@@ -79,8 +76,8 @@ private static void writeValue(FieldReader reader, FieldWriter writer) {
FieldReader structReader = reader.reader();
if (structReader.isSet()) {
writer.startEntry();
- writeValue(mapReader.key(), getMapWriterForReader(mapReader.key(), writer.key()));
- writeValue(mapReader.value(), getMapWriterForReader(mapReader.value(), writer.value()));
+ copy(mapReader.key(), getMapWriterForReader(mapReader.key(), writer.key()));
+ copy(mapReader.value(), getMapWriterForReader(mapReader.value(), writer.value()));
writer.endEntry();
} else {
writer.writeNull();
@@ -99,7 +96,7 @@ private static void writeValue(FieldReader reader, FieldWriter writer) {
if (childReader.getMinorType() != Types.MinorType.NULL) {
FieldWriter childWriter = getStructWriterForReader(childReader, writer, name);
if (childReader.isSet()) {
- writeValue(childReader, childWriter);
+ copy(childReader, childWriter);
} else {
childWriter.writeNull();
}
@@ -110,6 +107,16 @@ private static void writeValue(FieldReader reader, FieldWriter writer) {
writer.writeNull();
}
break;
+ case EXTENSIONTYPE:
+ if (reader.isSet()) {
+ Object value = reader.readObject();
+ if (value != null) {
+ writer.writeExtension(value, reader.getField().getType());
+ }
+ } else {
+ writer.writeNull();
+ }
+ break;
<#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/>
@@ -162,6 +169,9 @@ private static FieldWriter getStructWriterForReader(FieldReader reader, StructWr
return (FieldWriter) writer.map(name);
case LISTVIEW:
return (FieldWriter) writer.listView(name);
+ case EXTENSIONTYPE:
+ ExtensionWriter extensionWriter = writer.extension(name, reader.getField().getType());
+ return (FieldWriter) extensionWriter;
default:
throw new UnsupportedOperationException(reader.getMinorType().toString());
}
@@ -186,6 +196,9 @@ private static FieldWriter getListWriterForReader(FieldReader reader, ListWriter
return (FieldWriter) writer.list();
case LISTVIEW:
return (FieldWriter) writer.listView();
+ case EXTENSIONTYPE:
+ ExtensionWriter extensionWriter = writer.extension(reader.getField().getType());
+ return (FieldWriter) extensionWriter;
default:
throw new UnsupportedOperationException(reader.getMinorType().toString());
}
@@ -211,6 +224,9 @@ private static FieldWriter getMapWriterForReader(FieldReader reader, MapWriter w
return (FieldWriter) writer.listView();
case MAP:
return (FieldWriter) writer.map(false);
+ case EXTENSIONTYPE:
+ ExtensionWriter extensionWriter = writer.extension(reader.getField().getType());
+ return (FieldWriter) extensionWriter;
default:
throw new UnsupportedOperationException(reader.getMinorType().toString());
}
diff --git a/vector/src/main/codegen/templates/DenseUnionWriter.java b/vector/src/main/codegen/templates/DenseUnionWriter.java
index 8515b759e6..9aeea5b054 100644
--- a/vector/src/main/codegen/templates/DenseUnionWriter.java
+++ b/vector/src/main/codegen/templates/DenseUnionWriter.java
@@ -55,7 +55,9 @@ public DenseUnionWriter(DenseUnionVector vector, NullableStructWriterFactory nul
public void setPosition(int index) {
super.setPosition(index);
for (BaseWriter writer : writers) {
- writer.setPosition(index);
+ if (writer != null) {
+ writer.setPosition(index);
+ }
}
}
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/NullReader.java b/vector/src/main/codegen/templates/NullReader.java
index 1d77248e96..88e6ea98ea 100644
--- a/vector/src/main/codegen/templates/NullReader.java
+++ b/vector/src/main/codegen/templates/NullReader.java
@@ -86,6 +86,10 @@ public void read(int arrayIndex, Nullable${name}Holder holder){
}
#list>#list>
+ public void read(ExtensionHolder holder) {
+ holder.isSet = 0;
+ }
+
public int size(){
return 0;
}
diff --git a/vector/src/main/codegen/templates/PromotableWriter.java b/vector/src/main/codegen/templates/PromotableWriter.java
index 8d7d57bb9d..11d34f72c9 100644
--- a/vector/src/main/codegen/templates/PromotableWriter.java
+++ b/vector/src/main/codegen/templates/PromotableWriter.java
@@ -286,7 +286,7 @@ protected void setWriter(ValueVector v) {
writer = new UnionWriter((UnionVector) vector, nullableStructWriterFactory);
break;
case EXTENSIONTYPE:
- writer = new UnionExtensionWriter((ExtensionTypeVector) vector);
+ writer = ((ExtensionType) vector.getField().getType()).getNewFieldWriter(vector);
break;
default:
writer = type.getNewFieldWriter(vector);
@@ -541,13 +541,13 @@ public void writeLargeVarChar(String value) {
}
@Override
- public void writeExtension(Object value) {
- getWriter(MinorType.EXTENSIONTYPE).writeExtension(value);
+ public void writeExtension(Object value, ArrowType arrowType) {
+ getWriter(MinorType.EXTENSIONTYPE, arrowType).writeExtension(value, arrowType);
}
@Override
- public void addExtensionTypeWriterFactory(ExtensionTypeWriterFactory factory) {
- getWriter(MinorType.EXTENSIONTYPE).addExtensionTypeWriterFactory(factory);
+ public void write(ExtensionHolder holder) {
+ getWriter(MinorType.EXTENSIONTYPE, holder.type()).write(holder);
}
@Override
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 9424533f29..394348f029 100644
--- a/vector/src/main/codegen/templates/UnionListWriter.java
+++ b/vector/src/main/codegen/templates/UnionListWriter.java
@@ -53,6 +53,7 @@ public class Union${listName}Writer extends AbstractFieldWriter {
private boolean inStruct = false;
private boolean listStarted = false;
private String structName;
+ private ArrowType extensionType;
<#if listName == "LargeList" || listName == "LargeListView">
private static final long OFFSET_WIDTH = 8;
<#else>
@@ -122,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;
@@ -203,13 +202,13 @@ public MapWriter map(String name, boolean keysSorted) {
@Override
public ExtensionWriter extension(ArrowType arrowType) {
- writer.extension(arrowType);
- return writer;
+ extensionType = arrowType;
+ return this;
}
+
@Override
public ExtensionWriter extension(String name, ArrowType arrowType) {
- ExtensionWriter extensionWriter = writer.extension(name, arrowType);
- return extensionWriter;
+ return writer.extension(name, arrowType);
}
<#if listName == "LargeList">
@@ -336,14 +335,18 @@ public void writeNull() {
@Override
public void writeExtension(Object value) {
- writer.writeExtension(value);
+ writer.writeExtension(value, extensionType);
+ writer.setPosition(writer.idx() + 1);
}
+
@Override
- public void addExtensionTypeWriterFactory(ExtensionTypeWriterFactory var1) {
- writer.addExtensionTypeWriterFactory(var1);
+ public void writeExtension(Object value, ArrowType type) {
+ writeExtension(value);
}
+
public void write(ExtensionHolder var1) {
writer.write(var1);
+ writer.setPosition(writer.idx() + 1);
}
<#list vv.types as type>
@@ -365,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);
@@ -376,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);
@@ -424,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/codegen/templates/UnionMapWriter.java b/vector/src/main/codegen/templates/UnionMapWriter.java
index 8b2f091215..8bbf6ae0a4 100644
--- a/vector/src/main/codegen/templates/UnionMapWriter.java
+++ b/vector/src/main/codegen/templates/UnionMapWriter.java
@@ -243,4 +243,27 @@ public ExtensionWriter extension(ArrowType type) {
return super.extension(type);
}
}
+
+ public FixedSizeBinaryWriter fixedSizeBinary(int byteWidth) {
+ switch (mode) {
+ case KEY:
+ return entryWriter.fixedSizeBinary(MapVector.KEY_NAME, byteWidth);
+ case VALUE:
+ return entryWriter.fixedSizeBinary(MapVector.VALUE_NAME, byteWidth);
+ default:
+ return this;
+ }
+ }
+
+ @Override
+ public FixedSizeBinaryWriter fixedSizeBinary() {
+ switch (mode) {
+ case KEY:
+ return entryWriter.fixedSizeBinary(MapVector.KEY_NAME);
+ case VALUE:
+ return entryWriter.fixedSizeBinary(MapVector.VALUE_NAME);
+ default:
+ return this;
+ }
+ }
}
diff --git a/vector/src/main/codegen/templates/UnionReader.java b/vector/src/main/codegen/templates/UnionReader.java
index 96ad3e1b9b..0edae7ade0 100644
--- a/vector/src/main/codegen/templates/UnionReader.java
+++ b/vector/src/main/codegen/templates/UnionReader.java
@@ -79,6 +79,10 @@ public void read(int index, UnionHolder holder) {
}
private FieldReader getReaderForIndex(int index) {
+ return getReaderForIndex(index, null);
+ }
+
+ private FieldReader getReaderForIndex(int index, ArrowType type) {
int typeValue = data.getTypeValue(index);
FieldReader reader = (FieldReader) readers[typeValue];
if (reader != null) {
@@ -105,11 +109,26 @@ private FieldReader getReaderForIndex(int index) {
#if>
#list>
#list>
+ case EXTENSIONTYPE:
+ if(type == null) {
+ throw new RuntimeException("Cannot get Extension reader without an ArrowType");
+ }
+ return (FieldReader) getExtension(type);
default:
throw new UnsupportedOperationException("Unsupported type: " + MinorType.values()[typeValue]);
}
}
+ private ExtensionReader extensionReader;
+
+ private ExtensionReader getExtension(ArrowType type) {
+ if (extensionReader == null) {
+ extensionReader = data.getExtension(type).getReader();
+ extensionReader.setPosition(idx());
+ }
+ return extensionReader;
+ }
+
private SingleStructReaderImpl structReader;
private StructReader getStruct() {
@@ -240,4 +259,8 @@ public FieldReader reader() {
public boolean next() {
return getReaderForIndex(idx()).next();
}
+
+ public void read(ExtensionHolder holder){
+ getReaderForIndex(idx(), holder.type()).read(holder);
+ }
}
diff --git a/vector/src/main/codegen/templates/UnionVector.java b/vector/src/main/codegen/templates/UnionVector.java
index 67efdf60f7..c706591966 100644
--- a/vector/src/main/codegen/templates/UnionVector.java
+++ b/vector/src/main/codegen/templates/UnionVector.java
@@ -379,6 +379,22 @@ public MapVector getMap(String name, ArrowType arrowType) {
return mapVector;
}
+ private ExtensionTypeVector extensionVector;
+
+ public ExtensionTypeVector getExtension(ArrowType arrowType) {
+ if (extensionVector == null) {
+ int vectorCount = internalStruct.size();
+ extensionVector = addOrGet(null, MinorType.EXTENSIONTYPE, arrowType, ExtensionTypeVector.class);
+ if (internalStruct.size() > vectorCount) {
+ extensionVector.allocateNew();
+ if (callBack != null) {
+ callBack.doWork();
+ }
+ }
+ }
+ return extensionVector;
+ }
+
public int getTypeValue(int index) {
return typeBuffer.getByte(index * TYPE_WIDTH);
}
@@ -725,6 +741,8 @@ public ValueVector getVectorByType(int typeId, ArrowType arrowType) {
return getListView();
case MAP:
return getMap(name, arrowType);
+ case EXTENSIONTYPE:
+ return getExtension(arrowType);
default:
throw new UnsupportedOperationException("Cannot support type: " + MinorType.values()[typeId]);
}
diff --git a/vector/src/main/codegen/templates/UnionWriter.java b/vector/src/main/codegen/templates/UnionWriter.java
index 272edab17c..0db699fd8c 100644
--- a/vector/src/main/codegen/templates/UnionWriter.java
+++ b/vector/src/main/codegen/templates/UnionWriter.java
@@ -28,6 +28,8 @@
package org.apache.arrow.vector.complex.impl;
<#include "/@includes/vv_imports.ftl" />
+import java.util.HashMap;
+
import org.apache.arrow.vector.complex.writer.BaseWriter;
import org.apache.arrow.vector.types.Types.MinorType;
@@ -213,8 +215,31 @@ public MapWriter asMap(ArrowType arrowType) {
return getMapWriter(arrowType);
}
+ private java.util.Map extensionWriters = new HashMap<>();
+
private ExtensionWriter getExtensionWriter(ArrowType arrowType) {
- throw new UnsupportedOperationException("ExtensionTypes are not supported yet.");
+ ExtensionWriter w = extensionWriters.get(arrowType);
+ if (w == null) {
+ w = ((ExtensionType) arrowType).getNewFieldWriter(data.getExtension(arrowType));
+ w.setPosition(idx());
+ extensionWriters.put(arrowType, w);
+ }
+ return w;
+ }
+
+ public void writeExtension(Object value, ArrowType type) {
+ data.setType(idx(), MinorType.EXTENSIONTYPE);
+ ExtensionWriter w = getExtensionWriter(type);
+ w.setPosition(idx());
+ w.writeExtension(value);
+ }
+
+ @Override
+ public void write(ExtensionHolder holder) {
+ data.setType(idx(), MinorType.EXTENSIONTYPE);
+ ExtensionWriter w = getExtensionWriter(holder.type());
+ w.setPosition(idx());
+ w.write(holder);
}
BaseWriter getWriter(MinorType minorType) {
diff --git a/vector/src/main/java/org/apache/arrow/vector/BaseFixedWidthVector.java b/vector/src/main/java/org/apache/arrow/vector/BaseFixedWidthVector.java
index 4be55396b7..df1ac74f9b 100644
--- a/vector/src/main/java/org/apache/arrow/vector/BaseFixedWidthVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/BaseFixedWidthVector.java
@@ -49,9 +49,7 @@ public abstract class BaseFixedWidthVector extends BaseValueVector
protected final Field field;
private int allocationMonitor;
- protected ArrowBuf validityBuffer;
protected ArrowBuf valueBuffer;
- protected int valueCount;
/**
* Constructs a new instance.
@@ -72,6 +70,7 @@ public BaseFixedWidthVector(Field field, final BufferAllocator allocator, final
refreshValueCapacity();
}
+ @Override
public int getTypeWidth() {
return typeWidth;
}
@@ -87,7 +86,7 @@ public String getName() {
/* TODO:
* Once the entire hierarchy has been refactored, move common functions
- * like getNullCount(), splitAndTransferValidityBuffer to top level
+ * like getNullCount() to top level
* base class BaseValueVector.
*
* Along with this, some class members (validityBuffer) can also be
@@ -342,9 +341,9 @@ private void allocateBytes(int valueCount) {
* slice the source buffer so we have to explicitly allocate the validityBuffer of the target
* vector. This is unlike the databuffer which we can always slice for the target vector.
*/
- private void allocateValidityBuffer(final int validityBufferSize) {
- validityBuffer = allocator.buffer(validityBufferSize);
- validityBuffer.readerIndex(0);
+ @Override
+ protected void allocateValidityBuffer(final long validityBufferSize) {
+ super.allocateValidityBuffer(validityBufferSize);
refreshValueCapacity();
}
@@ -359,7 +358,7 @@ public int getBufferSizeFor(final int count) {
if (count == 0) {
return 0;
}
- return (count * typeWidth) + getValidityBufferSizeFromCount(count);
+ return (count * typeWidth) + BitVectorHelper.getValidityBufferSizeFromCount(count);
}
/**
@@ -372,7 +371,7 @@ public int getBufferSize() {
if (valueCount == 0) {
return 0;
}
- return (valueCount * typeWidth) + getValidityBufferSizeFromCount(valueCount);
+ return (valueCount * typeWidth) + BitVectorHelper.getValidityBufferSizeFromCount(valueCount);
}
/**
@@ -536,10 +535,10 @@ private void setReaderAndWriterIndex() {
validityBuffer.writerIndex(0);
valueBuffer.writerIndex(0);
} else {
- validityBuffer.writerIndex(getValidityBufferSizeFromCount(valueCount));
+ validityBuffer.writerIndex(BitVectorHelper.getValidityBufferSizeFromCount(valueCount));
if (typeWidth == 0) {
/* specialized handling for BitVector */
- valueBuffer.writerIndex(getValidityBufferSizeFromCount(valueCount));
+ valueBuffer.writerIndex(BitVectorHelper.getValidityBufferSizeFromCount(valueCount));
} else {
valueBuffer.writerIndex((long) valueCount * typeWidth);
}
@@ -656,72 +655,18 @@ private void splitAndTransferValueBuffer(
target.refreshValueCapacity();
}
- /**
- * Validity buffer has multiple cases of split and transfer depending on the starting position of
- * the source index.
- */
- private void splitAndTransferValidityBuffer(
- int startIndex, int length, BaseFixedWidthVector target) {
- int firstByteSource = BitVectorHelper.byteIndex(startIndex);
- int lastByteSource = BitVectorHelper.byteIndex(valueCount - 1);
- int byteSizeTarget = getValidityBufferSizeFromCount(length);
- int offset = startIndex % 8;
-
- if (length > 0) {
- if (offset == 0) {
- /* slice */
- if (target.validityBuffer != null) {
- target.validityBuffer.getReferenceManager().release();
- }
- ArrowBuf slicedValidityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
- target.validityBuffer = transferBuffer(slicedValidityBuffer, target.allocator);
- target.refreshValueCapacity();
- } else {
- /* Copy data
- * When the first bit starts from the middle of a byte (offset != 0),
- * copy data from src BitVector.
- * Each byte in the target is composed by a part in i-th byte,
- * another part in (i+1)-th byte.
- */
- target.allocateValidityBuffer(byteSizeTarget);
-
- for (int i = 0; i < byteSizeTarget - 1; i++) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- this.validityBuffer, firstByteSource + i, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- this.validityBuffer, firstByteSource + i + 1, offset);
-
- target.validityBuffer.setByte(i, (b1 + b2));
- }
-
- /* Copying the last piece is done in the following manner:
- * if the source vector has 1 or more bytes remaining, we copy
- * the last piece as a byte formed by shifting data
- * from the current byte and the next byte.
- *
- * if the source vector has no more bytes remaining
- * (we are at the last byte), we copy the last piece as a byte
- * by shifting data from the current byte.
- */
- if ((firstByteSource + byteSizeTarget - 1) < lastByteSource) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- this.validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- this.validityBuffer, firstByteSource + byteSizeTarget, offset);
-
- target.validityBuffer.setByte(byteSizeTarget - 1, b1 + b2);
- } else {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- this.validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- target.validityBuffer.setByte(byteSizeTarget - 1, b1);
- }
- }
+ @Override
+ protected void sliceAndTransferValidityBuffer(
+ int startIndex, int length, BaseValueVector target) {
+ final int firstByteSource = BitVectorHelper.byteIndex(startIndex);
+ final int byteSizeTarget = BitVectorHelper.getValidityBufferSizeFromCount(length);
+
+ if (target.validityBuffer != null) {
+ target.validityBuffer.getReferenceManager().release();
}
+ ArrowBuf slicedValidityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
+ target.validityBuffer = transferBuffer(slicedValidityBuffer, target.allocator);
+ ((BaseFixedWidthVector) target).refreshValueCapacity();
}
/*----------------------------------------------------------------*
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 7e0d0affc6..3fac195786 100644
--- a/vector/src/main/java/org/apache/arrow/vector/BaseLargeVariableWidthVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/BaseLargeVariableWidthVector.java
@@ -52,10 +52,8 @@ public abstract class BaseLargeVariableWidthVector extends BaseValueVector
/* protected members */
public static final int OFFSET_WIDTH = 8; /* 8 byte unsigned int to track offsets */
protected static final byte[] emptyByteArray = new byte[] {};
- protected ArrowBuf validityBuffer;
protected ArrowBuf valueBuffer;
protected ArrowBuf offsetBuffer;
- protected int valueCount;
protected int lastSet;
protected final Field field;
@@ -375,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(getValidityBufferSizeFromCount(valueCount));
- offsetBuffer.writerIndex((long) (valueCount + 1) * OFFSET_WIDTH);
+ validityBuffer.writerIndex(BitVectorHelper.getValidityBufferSizeFromCount(valueCount));
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()}. */
@@ -501,10 +511,9 @@ private ArrowBuf allocateOffsetBuffer(final long size) {
}
/* allocate validity buffer */
- private void allocateValidityBuffer(final long size) {
- validityBuffer = allocator.buffer(size);
- validityBuffer.readerIndex(0);
- initValidityBuffer();
+ @Override
+ protected void allocateValidityBuffer(final long size) {
+ super.allocateValidityBuffer(size);
}
/**
@@ -633,7 +642,7 @@ public int getBufferSizeFor(final int valueCount) {
return 0;
}
- final long validityBufferSize = getValidityBufferSizeFromCount(valueCount);
+ final long validityBufferSize = BitVectorHelper.getValidityBufferSizeFromCount(valueCount);
final long offsetBufferSize = (long) (valueCount + 1) * OFFSET_WIDTH;
/* get the end offset for this valueCount */
final long dataBufferSize = getStartOffset(valueCount);
@@ -809,69 +818,17 @@ private void splitAndTransferOffsetBuffer(
target.valueBuffer = transferBuffer(slicedBuffer, target.allocator);
}
- /*
- * Transfer the validity.
- */
- private void splitAndTransferValidityBuffer(
- int startIndex, int length, BaseLargeVariableWidthVector target) {
- int firstByteSource = BitVectorHelper.byteIndex(startIndex);
- int lastByteSource = BitVectorHelper.byteIndex(valueCount - 1);
- int byteSizeTarget = getValidityBufferSizeFromCount(length);
- int offset = startIndex % 8;
+ @Override
+ protected void sliceAndTransferValidityBuffer(
+ int startIndex, int length, BaseValueVector target) {
+ final int firstByteSource = BitVectorHelper.byteIndex(startIndex);
+ final int byteSizeTarget = BitVectorHelper.getValidityBufferSizeFromCount(length);
- if (length > 0) {
- if (offset == 0) {
- // slice
- if (target.validityBuffer != null) {
- target.validityBuffer.getReferenceManager().release();
- }
- target.validityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
- target.validityBuffer.getReferenceManager().retain();
- } else {
- /* Copy data
- * When the first bit starts from the middle of a byte (offset != 0),
- * copy data from src BitVector.
- * Each byte in the target is composed by a part in i-th byte,
- * another part in (i+1)-th byte.
- */
- target.allocateValidityBuffer(byteSizeTarget);
-
- for (int i = 0; i < byteSizeTarget - 1; i++) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- this.validityBuffer, firstByteSource + i, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- this.validityBuffer, firstByteSource + i + 1, offset);
-
- target.validityBuffer.setByte(i, (b1 + b2));
- }
- /* Copying the last piece is done in the following manner:
- * if the source vector has 1 or more bytes remaining, we copy
- * the last piece as a byte formed by shifting data
- * from the current byte and the next byte.
- *
- * if the source vector has no more bytes remaining
- * (we are at the last byte), we copy the last piece as a byte
- * by shifting data from the current byte.
- */
- if ((firstByteSource + byteSizeTarget - 1) < lastByteSource) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- this.validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- this.validityBuffer, firstByteSource + byteSizeTarget, offset);
-
- target.validityBuffer.setByte(byteSizeTarget - 1, b1 + b2);
- } else {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- this.validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- target.validityBuffer.setByte(byteSizeTarget - 1, b1);
- }
- }
+ if (target.validityBuffer != null) {
+ target.validityBuffer.getReferenceManager().release();
}
+ target.validityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
+ target.validityBuffer.getReferenceManager().retain();
}
/*----------------------------------------------------------------*
diff --git a/vector/src/main/java/org/apache/arrow/vector/BaseValueVector.java b/vector/src/main/java/org/apache/arrow/vector/BaseValueVector.java
index 9befcb890f..37dfa20616 100644
--- a/vector/src/main/java/org/apache/arrow/vector/BaseValueVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/BaseValueVector.java
@@ -48,6 +48,10 @@ public abstract class BaseValueVector implements ValueVector {
protected volatile FieldReader fieldReader;
+ protected ArrowBuf validityBuffer;
+
+ protected int valueCount;
+
protected BaseValueVector(BufferAllocator allocator) {
this.allocator = Preconditions.checkNotNull(allocator, "allocator cannot be null");
}
@@ -110,7 +114,14 @@ protected ArrowBuf releaseBuffer(ArrowBuf buffer) {
return buffer;
}
- /* number of bytes for the validity buffer for the given valueCount */
+ /**
+ * Compute the size of validity buffer required to manage a given number of elements in a vector.
+ *
+ * @param valueCount number of elements in the vector
+ * @return buffer size
+ * @deprecated -- use {@link BitVectorHelper#getValidityBufferSizeFromCount} instead.
+ */
+ @Deprecated(forRemoval = true, since = "18.4.0")
protected static int getValidityBufferSizeFromCount(final int valueCount) {
return DataSizeRoundingUtil.divideBy8Ceil(valueCount);
}
@@ -248,4 +259,116 @@ public void copyFrom(int fromIndex, int thisIndex, ValueVector from) {
public void copyFromSafe(int fromIndex, int thisIndex, ValueVector from) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * Transfer the validity buffer from `validityBuffer` to the target vector's `validityBuffer`.
+ * Start at `startIndex` and copy `length` number of elements. If the starting index is 8 byte
+ * aligned, then the buffer is sliced from that index and ownership is transferred. If not,
+ * individual bytes are copied.
+ *
+ * @param startIndex starting index
+ * @param length number of elements to be copied
+ * @param target target vector
+ */
+ protected void splitAndTransferValidityBuffer(
+ int startIndex, int length, BaseValueVector target) {
+ int offset = startIndex % 8;
+
+ if (length <= 0) {
+ return;
+ }
+ if (offset == 0) {
+ sliceAndTransferValidityBuffer(startIndex, length, target);
+ } else {
+ copyValidityBuffer(startIndex, length, target);
+ }
+ }
+
+ /**
+ * If the start index is 8 byte aligned, slice `validityBuffer` and transfer ownership to
+ * `target`'s `validityBuffer`.
+ *
+ * @param startIndex starting index
+ * @param length number of elements to be copied
+ * @param target target vector
+ */
+ protected void sliceAndTransferValidityBuffer(
+ int startIndex, int length, BaseValueVector target) {
+ final int firstByteSource = BitVectorHelper.byteIndex(startIndex);
+ final int byteSizeTarget = getValidityBufferSizeFromCount(length);
+
+ if (target.validityBuffer != null) {
+ target.validityBuffer.getReferenceManager().release();
+ }
+ target.validityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
+ target.validityBuffer.getReferenceManager().retain(1);
+ }
+
+ /**
+ * Allocate new validity buffer for `target` and copy bytes from `validityBuffer`. Precise details
+ * in the comments below.
+ *
+ * @param startIndex starting index
+ * @param length number of elements to be copied
+ * @param target target vector
+ */
+ protected void copyValidityBuffer(int startIndex, int length, BaseValueVector target) {
+ final int firstByteSource = BitVectorHelper.byteIndex(startIndex);
+ final int lastByteSource = BitVectorHelper.byteIndex(valueCount - 1);
+ final int byteSizeTarget = getValidityBufferSizeFromCount(length);
+ final int offset = startIndex % 8;
+
+ /* Copy data
+ * When the first bit starts from the middle of a byte (offset != 0),
+ * copy data from src BitVector.
+ * Each byte in the target is composed by a part in i-th byte,
+ * another part in (i+1)-th byte.
+ */
+ target.allocateValidityBuffer(byteSizeTarget);
+
+ for (int i = 0; i < byteSizeTarget - 1; i++) {
+ byte b1 =
+ BitVectorHelper.getBitsFromCurrentByte(this.validityBuffer, firstByteSource + i, offset);
+ byte b2 =
+ BitVectorHelper.getBitsFromNextByte(this.validityBuffer, firstByteSource + i + 1, offset);
+
+ target.validityBuffer.setByte(i, (b1 + b2));
+ }
+
+ /* Copying the last piece is done in the following manner:
+ * if the source vector has 1 or more bytes remaining, we copy
+ * the last piece as a byte formed by shifting data
+ * from the current byte and the next byte.
+ *
+ * if the source vector has no more bytes remaining
+ * (we are at the last byte), we copy the last piece as a byte
+ * by shifting data from the current byte.
+ */
+ if ((firstByteSource + byteSizeTarget - 1) < lastByteSource) {
+ byte b1 =
+ BitVectorHelper.getBitsFromCurrentByte(
+ this.validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
+ byte b2 =
+ BitVectorHelper.getBitsFromNextByte(
+ this.validityBuffer, firstByteSource + byteSizeTarget, offset);
+
+ target.validityBuffer.setByte(byteSizeTarget - 1, b1 + b2);
+ } else {
+ byte b1 =
+ BitVectorHelper.getBitsFromCurrentByte(
+ this.validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
+ target.validityBuffer.setByte(byteSizeTarget - 1, b1);
+ }
+ }
+
+ /**
+ * Allocate new validity buffer for when the bytes need to be copied over.
+ *
+ * @param byteSizeTarget desired size of the buffer
+ */
+ protected void allocateValidityBuffer(long byteSizeTarget) {
+ validityBuffer = allocator.buffer(byteSizeTarget);
+ validityBuffer.readerIndex(0);
+ validityBuffer.setZero(0, validityBuffer.capacity());
+ }
}
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 1609e64ca5..d5bd167256 100644
--- a/vector/src/main/java/org/apache/arrow/vector/BaseVariableWidthVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/BaseVariableWidthVector.java
@@ -50,10 +50,8 @@ public abstract class BaseVariableWidthVector extends BaseValueVector
/* protected members */
public static final int OFFSET_WIDTH = 4; /* 4 byte unsigned int to track offsets */
protected static final byte[] emptyByteArray = new byte[] {};
- protected ArrowBuf validityBuffer;
protected ArrowBuf valueBuffer;
protected ArrowBuf offsetBuffer;
- protected int valueCount;
protected int lastSet;
protected final Field field;
@@ -87,7 +85,7 @@ public String getName() {
/* TODO:
* Once the entire hierarchy has been refactored, move common functions
- * like getNullCount(), splitAndTransferValidityBuffer to top level
+ * like getNullCount() to top level
* base class BaseValueVector.
*
* Along with this, some class members (validityBuffer) can also be
@@ -391,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(getValidityBufferSizeFromCount(valueCount));
- offsetBuffer.writerIndex((long) (valueCount + 1) * OFFSET_WIDTH);
+ validityBuffer.writerIndex(BitVectorHelper.getValidityBufferSizeFromCount(valueCount));
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()}. */
@@ -519,11 +529,9 @@ private ArrowBuf allocateOffsetBuffer(final long size) {
}
/* allocate validity buffer */
- private void allocateValidityBuffer(final long size) {
- final int curSize = (int) size;
- validityBuffer = allocator.buffer(curSize);
- validityBuffer.readerIndex(0);
- initValidityBuffer();
+ @Override
+ protected void allocateValidityBuffer(final long size) {
+ super.allocateValidityBuffer(size);
}
/**
@@ -673,7 +681,7 @@ public int getBufferSizeFor(final int valueCount) {
return 0;
}
- final int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
+ final int validityBufferSize = BitVectorHelper.getValidityBufferSizeFromCount(valueCount);
final int offsetBufferSize = (valueCount + 1) * OFFSET_WIDTH;
/* get the end offset for this valueCount */
final int dataBufferSize = offsetBuffer.getInt((long) valueCount * OFFSET_WIDTH);
@@ -856,70 +864,17 @@ private void splitAndTransferOffsetBuffer(
target.valueBuffer = transferBuffer(slicedBuffer, target.allocator);
}
- /*
- * Transfer the validity.
- */
- private void splitAndTransferValidityBuffer(
- int startIndex, int length, BaseVariableWidthVector target) {
- if (length <= 0) {
- return;
- }
-
+ @Override
+ protected void sliceAndTransferValidityBuffer(
+ int startIndex, int length, BaseValueVector target) {
final int firstByteSource = BitVectorHelper.byteIndex(startIndex);
- final int lastByteSource = BitVectorHelper.byteIndex(valueCount - 1);
- final int byteSizeTarget = getValidityBufferSizeFromCount(length);
- final int offset = startIndex % 8;
-
- if (offset == 0) {
- // slice
- if (target.validityBuffer != null) {
- target.validityBuffer.getReferenceManager().release();
- }
- final ArrowBuf slicedValidityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
- target.validityBuffer = transferBuffer(slicedValidityBuffer, target.allocator);
- return;
- }
-
- /* Copy data
- * When the first bit starts from the middle of a byte (offset != 0),
- * copy data from src BitVector.
- * Each byte in the target is composed by a part in i-th byte,
- * another part in (i+1)-th byte.
- */
- target.allocateValidityBuffer(byteSizeTarget);
+ final int byteSizeTarget = BitVectorHelper.getValidityBufferSizeFromCount(length);
- for (int i = 0; i < byteSizeTarget - 1; i++) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(this.validityBuffer, firstByteSource + i, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(this.validityBuffer, firstByteSource + i + 1, offset);
-
- target.validityBuffer.setByte(i, (b1 + b2));
- }
- /* Copying the last piece is done in the following manner:
- * if the source vector has 1 or more bytes remaining, we copy
- * the last piece as a byte formed by shifting data
- * from the current byte and the next byte.
- *
- * if the source vector has no more bytes remaining
- * (we are at the last byte), we copy the last piece as a byte
- * by shifting data from the current byte.
- */
- if ((firstByteSource + byteSizeTarget - 1) < lastByteSource) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- this.validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- this.validityBuffer, firstByteSource + byteSizeTarget, offset);
-
- target.validityBuffer.setByte(byteSizeTarget - 1, b1 + b2);
- } else {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- this.validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- target.validityBuffer.setByte(byteSizeTarget - 1, b1);
+ if (target.validityBuffer != null) {
+ target.validityBuffer.getReferenceManager().release();
}
+ final ArrowBuf slicedValidityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
+ target.validityBuffer = transferBuffer(slicedValidityBuffer, target.allocator);
}
/*----------------------------------------------------------------*
diff --git a/vector/src/main/java/org/apache/arrow/vector/BaseVariableWidthViewVector.java b/vector/src/main/java/org/apache/arrow/vector/BaseVariableWidthViewVector.java
index beda91dc3f..ea9de8320e 100644
--- a/vector/src/main/java/org/apache/arrow/vector/BaseVariableWidthViewVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/BaseVariableWidthViewVector.java
@@ -78,13 +78,11 @@ public abstract class BaseVariableWidthViewVector extends BaseValueVector
// The third 4 bytes of view are allocated for buffer index
public static final int BUF_INDEX_WIDTH = 4;
public static final byte[] EMPTY_BYTE_ARRAY = new byte[] {};
- protected ArrowBuf validityBuffer;
// The view buffer is used to store the variable width view elements
protected ArrowBuf viewBuffer;
// The external buffer which stores the long strings
protected List dataBuffers;
protected int initialDataBufferSize;
- protected int valueCount;
protected int lastSet;
protected final Field field;
@@ -117,7 +115,7 @@ public String getName() {
/* TODO:
* Once the entire hierarchy has been refactored, move common functions
- * like getNullCount(), splitAndTransferValidityBuffer to top level
+ * like getNullCount() to top level
* base class BaseValueVector.
*
* Along with this, some class members (validityBuffer) can also be
@@ -129,12 +127,6 @@ public String getName() {
* the top class as of now is not a good idea.
*/
- /* TODO:
- * Implement TransferPair functionality
- * https://github.com/apache/arrow/issues/40932
- *
- */
-
/**
* Get buffer that manages the validity (NULL or NON-NULL nature) of elements in the vector.
* Consider it as a buffer for internal bit vector data structure.
@@ -400,7 +392,7 @@ private void setReaderAndWriterIndex() {
validityBuffer.writerIndex(0);
viewBuffer.writerIndex(0);
} else {
- validityBuffer.writerIndex(getValidityBufferSizeFromCount(valueCount));
+ validityBuffer.writerIndex(BitVectorHelper.getValidityBufferSizeFromCount(valueCount));
viewBuffer.writerIndex(valueCount * ELEMENT_SIZE);
}
}
@@ -683,7 +675,7 @@ public int getBufferSizeFor(final int valueCount) {
return 0;
}
- final int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
+ final int validityBufferSize = BitVectorHelper.getValidityBufferSizeFromCount(valueCount);
final int viewBufferSize = valueCount * ELEMENT_SIZE;
final int dataBufferSize = getDataBufferSize();
return validityBufferSize + viewBufferSize + dataBufferSize;
@@ -854,77 +846,22 @@ public void splitAndTransferTo(int startIndex, int length, BaseVariableWidthView
}
/* allocate validity buffer */
- private void allocateValidityBuffer(final long size) {
- final int curSize = (int) size;
- validityBuffer = allocator.buffer(curSize);
- validityBuffer.readerIndex(0);
- initValidityBuffer();
+ @Override
+ protected void allocateValidityBuffer(final long size) {
+ super.allocateValidityBuffer(size);
}
- /*
- * Transfer the validity.
- */
- private void splitAndTransferValidityBuffer(
- int startIndex, int length, BaseVariableWidthViewVector target) {
- if (length <= 0) {
- return;
- }
-
+ @Override
+ protected void sliceAndTransferValidityBuffer(
+ int startIndex, int length, BaseValueVector target) {
final int firstByteSource = BitVectorHelper.byteIndex(startIndex);
- final int lastByteSource = BitVectorHelper.byteIndex(valueCount - 1);
- final int byteSizeTarget = getValidityBufferSizeFromCount(length);
- final int offset = startIndex % 8;
-
- if (offset == 0) {
- // slice
- if (target.validityBuffer != null) {
- target.validityBuffer.getReferenceManager().release();
- }
- final ArrowBuf slicedValidityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
- target.validityBuffer = transferBuffer(slicedValidityBuffer, target.allocator);
- return;
- }
+ final int byteSizeTarget = BitVectorHelper.getValidityBufferSizeFromCount(length);
- /* Copy data
- * When the first bit starts from the middle of a byte (offset != 0),
- * copy data from src BitVector.
- * Each byte in the target is composed by a part in i-th byte,
- * another part in (i+1)-th byte.
- */
- target.allocateValidityBuffer(byteSizeTarget);
-
- for (int i = 0; i < byteSizeTarget - 1; i++) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(this.validityBuffer, firstByteSource + i, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(this.validityBuffer, firstByteSource + i + 1, offset);
-
- target.validityBuffer.setByte(i, (b1 + b2));
- }
- /* Copying the last piece is done in the following manner:
- * if the source vector has 1 or more bytes remaining, we copy
- * the last piece as a byte formed by shifting data
- * from the current byte and the next byte.
- *
- * if the source vector has no more bytes remaining
- * (we are at the last byte), we copy the last piece as a byte
- * by shifting data from the current byte.
- */
- if ((firstByteSource + byteSizeTarget - 1) < lastByteSource) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- this.validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- this.validityBuffer, firstByteSource + byteSizeTarget, offset);
-
- target.validityBuffer.setByte(byteSizeTarget - 1, b1 + b2);
- } else {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- this.validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- target.validityBuffer.setByte(byteSizeTarget - 1, b1);
+ if (target.validityBuffer != null) {
+ target.validityBuffer.getReferenceManager().release();
}
+ final ArrowBuf slicedValidityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
+ target.validityBuffer = transferBuffer(slicedValidityBuffer, target.allocator);
}
/**
diff --git a/vector/src/main/java/org/apache/arrow/vector/BitVector.java b/vector/src/main/java/org/apache/arrow/vector/BitVector.java
index f8e3342625..ecee02f665 100644
--- a/vector/src/main/java/org/apache/arrow/vector/BitVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/BitVector.java
@@ -98,7 +98,7 @@ public MinorType getMinorType() {
*/
@Override
public void setInitialCapacity(int valueCount) {
- final int size = getValidityBufferSizeFromCount(valueCount);
+ final int size = BitVectorHelper.getValidityBufferSizeFromCount(valueCount);
if (size * 2L > MAX_ALLOCATION_SIZE) {
throw new OversizedAllocationException("Requested amount of memory is more than max allowed");
}
@@ -121,7 +121,7 @@ public int getBufferSizeFor(final int count) {
if (count == 0) {
return 0;
}
- return 2 * getValidityBufferSizeFromCount(count);
+ return 2 * BitVectorHelper.getValidityBufferSizeFromCount(count);
}
/**
@@ -165,7 +165,7 @@ private ArrowBuf splitAndTransferBuffer(
int startIndex, int length, ArrowBuf sourceBuffer, ArrowBuf destBuffer) {
int firstByteSource = BitVectorHelper.byteIndex(startIndex);
int lastByteSource = BitVectorHelper.byteIndex(valueCount - 1);
- int byteSizeTarget = getValidityBufferSizeFromCount(length);
+ int byteSizeTarget = BitVectorHelper.getValidityBufferSizeFromCount(length);
int offset = startIndex % 8;
if (length > 0) {
diff --git a/vector/src/main/java/org/apache/arrow/vector/BitVectorHelper.java b/vector/src/main/java/org/apache/arrow/vector/BitVectorHelper.java
index 0ac56691a6..bc2c3da98f 100644
--- a/vector/src/main/java/org/apache/arrow/vector/BitVectorHelper.java
+++ b/vector/src/main/java/org/apache/arrow/vector/BitVectorHelper.java
@@ -135,11 +135,11 @@ public static void setValidityBit(ArrowBuf validityBuffer, int index, int value)
public static ArrowBuf setValidityBit(
ArrowBuf validityBuffer, BufferAllocator allocator, int valueCount, int index, int value) {
if (validityBuffer == null) {
- validityBuffer = allocator.buffer(getValidityBufferSize(valueCount));
+ validityBuffer = allocator.buffer(getValidityBufferSizeFromCount(valueCount));
}
setValidityBit(validityBuffer, index, value);
if (index == (valueCount - 1)) {
- validityBuffer.writerIndex(getValidityBufferSize(valueCount));
+ validityBuffer.writerIndex(getValidityBufferSizeFromCount(valueCount));
}
return validityBuffer;
@@ -165,7 +165,7 @@ public static int get(final ArrowBuf buffer, int index) {
* @param valueCount number of elements in the vector
* @return buffer size
*/
- public static int getValidityBufferSize(int valueCount) {
+ public static int getValidityBufferSizeFromCount(int valueCount) {
return DataSizeRoundingUtil.divideBy8Ceil(valueCount);
}
@@ -182,7 +182,7 @@ public static int getNullCount(final ArrowBuf validityBuffer, final int valueCou
return 0;
}
int count = 0;
- final int sizeInBytes = getValidityBufferSize(valueCount);
+ final int sizeInBytes = getValidityBufferSizeFromCount(valueCount);
// If value count is not a multiple of 8, then calculate number of used bits in the last byte
final int remainder = valueCount % 8;
final int fullBytesCount = remainder == 0 ? sizeInBytes : sizeInBytes - 1;
@@ -233,7 +233,7 @@ public static boolean checkAllBitsEqualTo(
if (valueCount == 0) {
return true;
}
- final int sizeInBytes = getValidityBufferSize(valueCount);
+ final int sizeInBytes = getValidityBufferSizeFromCount(valueCount);
// boundary check
validityBuffer.checkBytes(0, sizeInBytes);
@@ -325,7 +325,7 @@ public static ArrowBuf loadValidityBuffer(
sourceValidityBuffer == null || sourceValidityBuffer.capacity() == 0;
if (isValidityBufferNull
&& (fieldNode.getNullCount() == 0 || fieldNode.getNullCount() == valueCount)) {
- newBuffer = allocator.buffer(getValidityBufferSize(valueCount));
+ newBuffer = allocator.buffer(getValidityBufferSizeFromCount(valueCount));
newBuffer.setZero(0, newBuffer.capacity());
if (fieldNode.getNullCount() != 0) {
/* all NULLs */
diff --git a/vector/src/main/java/org/apache/arrow/vector/FixedWidthVector.java b/vector/src/main/java/org/apache/arrow/vector/FixedWidthVector.java
index e22a973f3b..61a5574898 100644
--- a/vector/src/main/java/org/apache/arrow/vector/FixedWidthVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/FixedWidthVector.java
@@ -31,4 +31,7 @@ public interface FixedWidthVector extends ElementAddressableVector {
/** Zero out the underlying buffer backing this vector. */
void zeroVector();
+
+ /** Get the width of the type in bytes. */
+ int getTypeWidth();
}
diff --git a/vector/src/main/java/org/apache/arrow/vector/TimeStampMicroTZVector.java b/vector/src/main/java/org/apache/arrow/vector/TimeStampMicroTZVector.java
index abaefcfc12..50f2f066cc 100644
--- a/vector/src/main/java/org/apache/arrow/vector/TimeStampMicroTZVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/TimeStampMicroTZVector.java
@@ -155,12 +155,13 @@ public void set(int index, NullableTimeStampMicroTZHolder holder)
throws IllegalArgumentException {
if (holder.isSet < 0) {
throw new IllegalArgumentException();
- } else if (!this.timeZone.equals(holder.timezone)) {
- throw new IllegalArgumentException(
- String.format(
- "holder.timezone: %s not equal to vector timezone: %s",
- holder.timezone, this.timeZone));
} else if (holder.isSet > 0) {
+ if (!this.timeZone.equals(holder.timezone)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "holder.timezone: %s not equal to vector timezone: %s",
+ holder.timezone, this.timeZone));
+ }
BitVectorHelper.setBit(validityBuffer, index);
setValue(index, holder.value);
} else {
diff --git a/vector/src/main/java/org/apache/arrow/vector/TimeStampMilliTZVector.java b/vector/src/main/java/org/apache/arrow/vector/TimeStampMilliTZVector.java
index b5e5fb1be1..9e4998396c 100644
--- a/vector/src/main/java/org/apache/arrow/vector/TimeStampMilliTZVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/TimeStampMilliTZVector.java
@@ -155,12 +155,13 @@ public void set(int index, NullableTimeStampMilliTZHolder holder)
throws IllegalArgumentException {
if (holder.isSet < 0) {
throw new IllegalArgumentException();
- } else if (!this.timeZone.equals(holder.timezone)) {
- throw new IllegalArgumentException(
- String.format(
- "holder.timezone: %s not equal to vector timezone: %s",
- holder.timezone, this.timeZone));
} else if (holder.isSet > 0) {
+ if (!this.timeZone.equals(holder.timezone)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "holder.timezone: %s not equal to vector timezone: %s",
+ holder.timezone, this.timeZone));
+ }
BitVectorHelper.setBit(validityBuffer, index);
setValue(index, holder.value);
} else {
diff --git a/vector/src/main/java/org/apache/arrow/vector/TimeStampNanoTZVector.java b/vector/src/main/java/org/apache/arrow/vector/TimeStampNanoTZVector.java
index 2386b3a859..b44b3da8d3 100644
--- a/vector/src/main/java/org/apache/arrow/vector/TimeStampNanoTZVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/TimeStampNanoTZVector.java
@@ -154,12 +154,13 @@ public Long getObject(int index) {
public void set(int index, NullableTimeStampNanoTZHolder holder) throws IllegalArgumentException {
if (holder.isSet < 0) {
throw new IllegalArgumentException();
- } else if (!this.timeZone.equals(holder.timezone)) {
- throw new IllegalArgumentException(
- String.format(
- "holder.timezone: %s not equal to vector timezone: %s",
- holder.timezone, this.timeZone));
} else if (holder.isSet > 0) {
+ if (!this.timeZone.equals(holder.timezone)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "holder.timezone: %s not equal to vector timezone: %s",
+ holder.timezone, this.timeZone));
+ }
BitVectorHelper.setBit(validityBuffer, index);
setValue(index, holder.value);
} else {
diff --git a/vector/src/main/java/org/apache/arrow/vector/TimeStampSecTZVector.java b/vector/src/main/java/org/apache/arrow/vector/TimeStampSecTZVector.java
index f1774f2703..a64a87f699 100644
--- a/vector/src/main/java/org/apache/arrow/vector/TimeStampSecTZVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/TimeStampSecTZVector.java
@@ -150,12 +150,13 @@ public Long getObject(int index) {
public void set(int index, NullableTimeStampSecTZHolder holder) throws IllegalArgumentException {
if (holder.isSet < 0) {
throw new IllegalArgumentException();
- } else if (!this.timeZone.equals(holder.timezone)) {
- throw new IllegalArgumentException(
- String.format(
- "holder.timezone: %s not equal to vector timezone: %s",
- holder.timezone, this.timeZone));
} else if (holder.isSet > 0) {
+ if (!this.timeZone.equals(holder.timezone)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "holder.timezone: %s not equal to vector timezone: %s",
+ holder.timezone, this.timeZone));
+ }
BitVectorHelper.setBit(validityBuffer, index);
setValue(index, holder.value);
} else {
diff --git a/vector/src/main/java/org/apache/arrow/vector/UuidVector.java b/vector/src/main/java/org/apache/arrow/vector/UuidVector.java
new file mode 100644
index 0000000000..e1e61a5a2e
--- /dev/null
+++ b/vector/src/main/java/org/apache/arrow/vector/UuidVector.java
@@ -0,0 +1,458 @@
+/*
+ * 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.vector;
+
+import static org.apache.arrow.vector.extension.UuidType.UUID_BYTE_WIDTH;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+import org.apache.arrow.memory.ArrowBuf;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.util.ArrowBufPointer;
+import org.apache.arrow.memory.util.ByteFunctionHelpers;
+import org.apache.arrow.memory.util.hash.ArrowBufHasher;
+import org.apache.arrow.util.Preconditions;
+import org.apache.arrow.vector.complex.impl.UuidReaderImpl;
+import org.apache.arrow.vector.complex.reader.FieldReader;
+import org.apache.arrow.vector.extension.UuidType;
+import org.apache.arrow.vector.holders.NullableUuidHolder;
+import org.apache.arrow.vector.holders.UuidHolder;
+import org.apache.arrow.vector.types.pojo.Field;
+import org.apache.arrow.vector.types.pojo.FieldType;
+import org.apache.arrow.vector.util.CallBack;
+import org.apache.arrow.vector.util.TransferPair;
+import org.apache.arrow.vector.util.UuidUtility;
+
+/**
+ * Vector implementation for UUID values using {@link UuidType}.
+ *
+ *
Supports setting and retrieving UUIDs with efficient storage and nullable value handling.
+ *
+ *
+ *
+ * @see UuidType
+ * @see UuidHolder
+ * @see NullableUuidHolder
+ */
+public class UuidVector extends ExtensionTypeVector
+ implements ValueIterableVector, FixedWidthVector {
+ private final Field field;
+
+ /** The fixed byte width of UUID values (16 bytes). */
+ public static final int TYPE_WIDTH = UUID_BYTE_WIDTH;
+
+ /**
+ * Constructs a UUID vector with the given name, allocator, and underlying vector.
+ *
+ * @param name the name of the vector
+ * @param allocator the buffer allocator
+ * @param underlyingVector the underlying FixedSizeBinaryVector for storage
+ */
+ public UuidVector(
+ String name, BufferAllocator allocator, FixedSizeBinaryVector underlyingVector) {
+ super(name, allocator, underlyingVector);
+ this.field = new Field(name, FieldType.nullable(UuidType.INSTANCE), null);
+ }
+
+ /**
+ * Constructs a UUID vector with the given name, field type, allocator, and underlying vector.
+ *
+ * @param name the name of the vector
+ * @param fieldType the field type (should contain UuidType)
+ * @param allocator the buffer allocator
+ * @param underlyingVector the underlying FixedSizeBinaryVector for storage
+ */
+ public UuidVector(
+ String name,
+ FieldType fieldType,
+ BufferAllocator allocator,
+ FixedSizeBinaryVector underlyingVector) {
+ super(name, allocator, underlyingVector);
+ this.field = new Field(name, fieldType, null);
+ }
+
+ /**
+ * Constructs a UUID vector with the given name and allocator.
+ *
+ *
Creates a new underlying FixedSizeBinaryVector with 16-byte width.
+ *
+ * @param name the name of the vector
+ * @param allocator the buffer allocator
+ */
+ public UuidVector(String name, BufferAllocator allocator) {
+ super(name, allocator, new FixedSizeBinaryVector(name, allocator, UUID_BYTE_WIDTH));
+ this.field = new Field(name, FieldType.nullable(UuidType.INSTANCE), null);
+ }
+
+ /**
+ * Constructs a UUID vector from a field and allocator.
+ *
+ * @param field the field definition (should contain UuidType)
+ * @param allocator the buffer allocator
+ */
+ public UuidVector(Field field, BufferAllocator allocator) {
+ super(
+ field.getName(),
+ allocator,
+ new FixedSizeBinaryVector(field.getName(), allocator, UUID_BYTE_WIDTH));
+ this.field = field;
+ }
+
+ @Override
+ public UUID getObject(int index) {
+ if (isSet(index) == 0) {
+ return null;
+ }
+ final ByteBuffer bb = ByteBuffer.wrap(getUnderlyingVector().getObject(index));
+ return new UUID(bb.getLong(), bb.getLong());
+ }
+
+ @Override
+ public int hashCode(int index) {
+ return hashCode(index, null);
+ }
+
+ @Override
+ public int hashCode(int index, ArrowBufHasher hasher) {
+ int start = this.getStartOffset(index);
+ return ByteFunctionHelpers.hash(hasher, this.getDataBuffer(), start, start + UUID_BYTE_WIDTH);
+ }
+
+ /**
+ * Checks if the value at the given index is set (non-null).
+ *
+ * @param index the index to check
+ * @return 1 if the value is set, 0 if null
+ */
+ public int isSet(int index) {
+ return getUnderlyingVector().isSet(index);
+ }
+
+ /**
+ * Reads the UUID value at the given index into a NullableUuidHolder.
+ *
+ * @param index the index to read from
+ * @param holder the holder to populate with the UUID data
+ */
+ public void get(int index, NullableUuidHolder holder) {
+ Preconditions.checkArgument(index >= 0, "Cannot get negative index in UUID vector.");
+ if (isSet(index) == 0) {
+ holder.isSet = 0;
+ return;
+ }
+ holder.isSet = 1;
+ holder.buffer = getDataBuffer();
+ holder.start = getStartOffset(index);
+ }
+
+ /**
+ * Calculates the byte offset for a given index in the data buffer.
+ *
+ * @param index the index of the UUID value
+ * @return the byte offset in the data buffer
+ */
+ public final int getStartOffset(int index) {
+ return index * UUID_BYTE_WIDTH;
+ }
+
+ /**
+ * Sets the UUID value at the given index.
+ *
+ * @param index the index to set
+ * @param value the UUID value to set, or null to set a null value
+ */
+ public void set(int index, UUID value) {
+ if (value != null) {
+ set(index, UuidUtility.getBytesFromUUID(value));
+ } else {
+ getUnderlyingVector().setNull(index);
+ }
+ }
+
+ /**
+ * Sets the UUID value at the given index from a UuidHolder.
+ *
+ * @param index the index to set
+ * @param holder the holder containing the UUID data
+ */
+ public void set(int index, UuidHolder holder) {
+ this.set(index, holder.buffer, holder.start);
+ }
+
+ /**
+ * Sets the UUID value at the given index from a NullableUuidHolder.
+ *
+ * @param index the index to set
+ * @param holder the holder containing the UUID data
+ */
+ public void set(int index, NullableUuidHolder holder) {
+ if (holder.isSet == 0) {
+ getUnderlyingVector().setNull(index);
+ } else {
+ this.set(index, holder.buffer, holder.start);
+ }
+ }
+
+ /**
+ * Sets the UUID value at the given index by copying from a source buffer.
+ *
+ * @param index the index to set
+ * @param source the source buffer to copy from
+ * @param sourceOffset the offset in the source buffer where the UUID data starts
+ */
+ public void set(int index, ArrowBuf source, int sourceOffset) {
+ Preconditions.checkNotNull(source, "Cannot set UUID vector, the source buffer is null.");
+
+ BitVectorHelper.setBit(getUnderlyingVector().getValidityBuffer(), index);
+ getUnderlyingVector()
+ .getDataBuffer()
+ .setBytes((long) index * UUID_BYTE_WIDTH, source, sourceOffset, UUID_BYTE_WIDTH);
+ }
+
+ /**
+ * Sets the UUID value at the given index from a byte array.
+ *
+ * @param index the index to set
+ * @param value the 16-byte array containing the UUID data
+ */
+ public void set(int index, byte[] value) {
+ getUnderlyingVector().set(index, value);
+ }
+
+ /**
+ * Sets the UUID value at the given index, expanding capacity if needed.
+ *
+ * @param index the index to set
+ * @param value the UUID value to set, or null to set a null value
+ */
+ public void setSafe(int index, UUID value) {
+ if (value != null) {
+ setSafe(index, UuidUtility.getBytesFromUUID(value));
+ } else {
+ getUnderlyingVector().setNull(index);
+ }
+ }
+
+ /**
+ * Sets the UUID value at the given index from a NullableUuidHolder, expanding capacity if needed.
+ *
+ * @param index the index to set
+ * @param holder the holder containing the UUID data, or null to set a null value
+ */
+ public void setSafe(int index, NullableUuidHolder holder) {
+ if (holder == null || holder.isSet == 0) {
+ getUnderlyingVector().setNull(index);
+ } else {
+ this.setSafe(index, holder.buffer, holder.start);
+ }
+ }
+
+ /**
+ * Sets the UUID value at the given index from a UuidHolder, expanding capacity if needed.
+ *
+ * @param index the index to set
+ * @param holder the holder containing the UUID data
+ */
+ public void setSafe(int index, UuidHolder holder) {
+ this.setSafe(index, holder.buffer, holder.start);
+ }
+
+ /**
+ * Sets the UUID value at the given index by copying from a source buffer, expanding capacity if
+ * needed.
+ *
+ * @param index the index to set
+ * @param buffer the source buffer to copy from
+ * @param start the offset in the source buffer where the UUID data starts
+ */
+ public void setSafe(int index, ArrowBuf buffer, int start) {
+ getUnderlyingVector().handleSafe(index);
+ this.set(index, buffer, start);
+ }
+
+ /**
+ * Sets the UUID value at the given index from a byte array, expanding capacity if needed.
+ *
+ * @param index the index to set
+ * @param value the 16-byte array containing the UUID data
+ */
+ public void setSafe(int index, byte[] value) {
+ getUnderlyingVector().setIndexDefined(index);
+ getUnderlyingVector().setSafe(index, value);
+ }
+
+ /**
+ * Sets the UUID value at the given index from an ArrowBuf, expanding capacity if needed.
+ *
+ * @param index the index to set
+ * @param value the buffer containing the 16-byte UUID data
+ */
+ public void setSafe(int index, ArrowBuf value) {
+ getUnderlyingVector().setSafe(index, value);
+ }
+
+ @Override
+ public void copyFrom(int fromIndex, int thisIndex, ValueVector from) {
+ getUnderlyingVector()
+ .copyFromSafe(fromIndex, thisIndex, ((UuidVector) from).getUnderlyingVector());
+ }
+
+ @Override
+ public void copyFromSafe(int fromIndex, int thisIndex, ValueVector from) {
+ getUnderlyingVector()
+ .copyFromSafe(fromIndex, thisIndex, ((UuidVector) from).getUnderlyingVector());
+ }
+
+ @Override
+ public Field getField() {
+ return field;
+ }
+
+ @Override
+ public ArrowBufPointer getDataPointer(int i) {
+ return getUnderlyingVector().getDataPointer(i);
+ }
+
+ @Override
+ public ArrowBufPointer getDataPointer(int i, ArrowBufPointer arrowBufPointer) {
+ return getUnderlyingVector().getDataPointer(i, arrowBufPointer);
+ }
+
+ @Override
+ public void allocateNew(int valueCount) {
+ getUnderlyingVector().allocateNew(valueCount);
+ }
+
+ @Override
+ public void zeroVector() {
+ getUnderlyingVector().zeroVector();
+ }
+
+ @Override
+ public TransferPair makeTransferPair(ValueVector to) {
+ return new TransferImpl((UuidVector) to);
+ }
+
+ @Override
+ protected FieldReader getReaderImpl() {
+ return new UuidReaderImpl(this);
+ }
+
+ @Override
+ public TransferPair getTransferPair(Field field, BufferAllocator allocator) {
+ return new TransferImpl(field, allocator);
+ }
+
+ @Override
+ public TransferPair getTransferPair(Field field, BufferAllocator allocator, CallBack callBack) {
+ return getTransferPair(field, allocator);
+ }
+
+ @Override
+ public TransferPair getTransferPair(String ref, BufferAllocator allocator) {
+ return new TransferImpl(ref, allocator);
+ }
+
+ @Override
+ public TransferPair getTransferPair(String ref, BufferAllocator allocator, CallBack callBack) {
+ return getTransferPair(ref, allocator);
+ }
+
+ @Override
+ public TransferPair getTransferPair(BufferAllocator allocator) {
+ return getTransferPair(this.getField().getName(), allocator);
+ }
+
+ @Override
+ public int getTypeWidth() {
+ return UUID_BYTE_WIDTH;
+ }
+
+ /** {@link TransferPair} for {@link UuidVector}. */
+ public class TransferImpl implements TransferPair {
+ UuidVector to;
+
+ /**
+ * Constructs a transfer pair with the given target vector.
+ *
+ * @param to the target UUID vector
+ */
+ public TransferImpl(UuidVector to) {
+ this.to = to;
+ }
+
+ /**
+ * Constructs a transfer pair, creating a new target vector from the field and allocator.
+ *
+ * @param field the field definition for the target vector
+ * @param allocator the buffer allocator for the target vector
+ */
+ public TransferImpl(Field field, BufferAllocator allocator) {
+ this.to = new UuidVector(field, allocator);
+ }
+
+ /**
+ * Constructs a transfer pair, creating a new target vector with the given name and allocator.
+ *
+ * @param ref the name for the target vector
+ * @param allocator the buffer allocator for the target vector
+ */
+ public TransferImpl(String ref, BufferAllocator allocator) {
+ this.to = new UuidVector(ref, allocator);
+ }
+
+ /**
+ * Gets the target vector of this transfer pair.
+ *
+ * @return the target UUID vector
+ */
+ public UuidVector getTo() {
+ return this.to;
+ }
+
+ /** Transfers ownership of data from the source vector to the target vector. */
+ public void transfer() {
+ getUnderlyingVector().transferTo(to.getUnderlyingVector());
+ }
+
+ /**
+ * Splits and transfers a range of values from the source vector to the target vector.
+ *
+ * @param startIndex the starting index in the source vector
+ * @param length the number of values to transfer
+ */
+ public void splitAndTransfer(int startIndex, int length) {
+ getUnderlyingVector().splitAndTransferTo(startIndex, length, to.getUnderlyingVector());
+ }
+
+ /**
+ * Copies a value from the source vector to the target vector, expanding capacity if needed.
+ *
+ * @param fromIndex the index in the source vector
+ * @param toIndex the index in the target vector
+ */
+ public void copyValueSafe(int fromIndex, int toIndex) {
+ to.copyFromSafe(fromIndex, toIndex, (ValueVector) UuidVector.this);
+ }
+ }
+}
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 newVectors = new ArrayList<>();
- for (int i = 0; i < fieldVectors.size(); i++) {
- if (i == index) {
- newVectors.add(vector);
+ if (index == fieldVectors.size()) {
+ newVectors.addAll(fieldVectors);
+ newVectors.add(vector);
+ } else {
+ for (int i = 0; i < fieldVectors.size(); i++) {
+ if (i == index) {
+ newVectors.add(vector);
+ }
+ newVectors.add(fieldVectors.get(i));
}
- newVectors.add(fieldVectors.get(i));
}
return new VectorSchemaRoot(newVectors);
}
diff --git a/vector/src/main/java/org/apache/arrow/vector/compare/RangeEqualsVisitor.java b/vector/src/main/java/org/apache/arrow/vector/compare/RangeEqualsVisitor.java
index abcf312c5e..bc2e3a6aab 100644
--- a/vector/src/main/java/org/apache/arrow/vector/compare/RangeEqualsVisitor.java
+++ b/vector/src/main/java/org/apache/arrow/vector/compare/RangeEqualsVisitor.java
@@ -43,6 +43,7 @@
import org.apache.arrow.vector.complex.ListViewVector;
import org.apache.arrow.vector.complex.NonNullableStructVector;
import org.apache.arrow.vector.complex.RunEndEncodedVector;
+import org.apache.arrow.vector.complex.RunEndEncodedVector.RangeIterator;
import org.apache.arrow.vector.complex.StructVector;
import org.apache.arrow.vector.complex.UnionVector;
@@ -270,42 +271,35 @@ protected boolean compareRunEndEncodedVectors(Range range) {
RunEndEncodedVector leftVector = (RunEndEncodedVector) left;
RunEndEncodedVector rightVector = (RunEndEncodedVector) right;
- final int leftRangeEnd = range.getLeftStart() + range.getLength();
- final int rightRangeEnd = range.getRightStart() + range.getLength();
+ final RunEndEncodedVector.RangeIterator leftIterator =
+ new RunEndEncodedVector.RangeIterator(leftVector, range.getLeftStart(), range.getLength());
+ final RunEndEncodedVector.RangeIterator rightIterator =
+ new RunEndEncodedVector.RangeIterator(
+ rightVector, range.getRightStart(), range.getLength());
FieldVector leftValuesVector = leftVector.getValuesVector();
FieldVector rightValuesVector = rightVector.getValuesVector();
RangeEqualsVisitor innerVisitor = createInnerVisitor(leftValuesVector, rightValuesVector, null);
- int leftLogicalIndex = range.getLeftStart();
- int rightLogicalIndex = range.getRightStart();
+ while (nextRun(leftIterator, rightIterator)) {
+ int leftPhysicalIndex = leftIterator.getRunIndex();
+ int rightPhysicalIndex = rightIterator.getRunIndex();
- while (leftLogicalIndex < leftRangeEnd) {
- // TODO: implement it more efficient
- // https://github.com/apache/arrow/issues/44157
- int leftPhysicalIndex = leftVector.getPhysicalIndex(leftLogicalIndex);
- int rightPhysicalIndex = rightVector.getPhysicalIndex(rightLogicalIndex);
- if (leftValuesVector.accept(
- innerVisitor, new Range(leftPhysicalIndex, rightPhysicalIndex, 1))) {
- int leftRunEnd = leftVector.getRunEnd(leftLogicalIndex);
- int rightRunEnd = rightVector.getRunEnd(rightLogicalIndex);
-
- int leftRunLength = Math.min(leftRunEnd, leftRangeEnd) - leftLogicalIndex;
- int rightRunLength = Math.min(rightRunEnd, rightRangeEnd) - rightLogicalIndex;
-
- if (leftRunLength != rightRunLength) {
- return false;
- } else {
- leftLogicalIndex = leftRunEnd;
- rightLogicalIndex = rightRunEnd;
- }
- } else {
+ if (leftIterator.getRunLength() != rightIterator.getRunLength()
+ || !leftValuesVector.accept(
+ innerVisitor, new Range(leftPhysicalIndex, rightPhysicalIndex, 1))) {
return false;
}
}
- return true;
+ return leftIterator.isEnd() && rightIterator.isEnd();
+ }
+
+ private static boolean nextRun(RangeIterator leftIterator, RangeIterator rightIterator) {
+ boolean left = leftIterator.nextRun();
+ boolean right = rightIterator.nextRun();
+ return left && right;
}
protected RangeEqualsVisitor createInnerVisitor(
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/AbstractStructVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/AbstractStructVector.java
index 2921e43cb6..a57fbe473f 100644
--- a/vector/src/main/java/org/apache/arrow/vector/complex/AbstractStructVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/AbstractStructVector.java
@@ -46,11 +46,13 @@ public abstract class AbstractStructVector extends AbstractContainerVector {
private ConflictPolicy conflictPolicy;
static {
- String conflictPolicyStr =
- System.getProperty(STRUCT_CONFLICT_POLICY_JVM, ConflictPolicy.CONFLICT_REPLACE.toString());
+ String conflictPolicyStr = System.getProperty(STRUCT_CONFLICT_POLICY_JVM);
if (conflictPolicyStr == null) {
conflictPolicyStr = System.getenv(STRUCT_CONFLICT_POLICY_ENV);
}
+ if (conflictPolicyStr == null) {
+ conflictPolicyStr = ConflictPolicy.CONFLICT_REPLACE.toString();
+ }
ConflictPolicy conflictPolicy;
try {
conflictPolicy = ConflictPolicy.valueOf(conflictPolicyStr.toUpperCase(Locale.ROOT));
@@ -62,11 +64,11 @@ public abstract class AbstractStructVector extends AbstractContainerVector {
/** Policy to determine how to react when duplicate columns are encountered. */
public enum ConflictPolicy {
- // Ignore the conflict and append the field. This is the default behaviour
+ // Ignore the conflict and append the field.
CONFLICT_APPEND,
// Keep the existing field and ignore the newer one.
CONFLICT_IGNORE,
- // Replace the existing field with the newer one.
+ // Replace the existing field with the newer one. This is the default behaviour
CONFLICT_REPLACE,
// Refuse the new field and error out.
CONFLICT_ERROR
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/BaseLargeRepeatedValueViewVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/BaseLargeRepeatedValueViewVector.java
index 12edd6557b..fac3f86bba 100644
--- a/vector/src/main/java/org/apache/arrow/vector/complex/BaseLargeRepeatedValueViewVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/BaseLargeRepeatedValueViewVector.java
@@ -52,7 +52,6 @@ public abstract class BaseLargeRepeatedValueViewVector extends BaseValueVector
protected ArrowBuf sizeBuffer;
protected FieldVector vector;
protected final CallBack repeatedCallBack;
- protected int valueCount;
protected long offsetAllocationSizeInBytes = INITIAL_VALUE_ALLOCATION * OFFSET_WIDTH;
protected long sizeAllocationSizeInBytes = INITIAL_VALUE_ALLOCATION * SIZE_WIDTH;
private final String name;
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/BaseRepeatedValueVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/BaseRepeatedValueVector.java
index fbe83bad52..ee1d65d3e3 100644
--- a/vector/src/main/java/org/apache/arrow/vector/complex/BaseRepeatedValueVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/BaseRepeatedValueVector.java
@@ -54,7 +54,6 @@ public abstract class BaseRepeatedValueVector extends BaseValueVector
protected ArrowBuf offsetBuffer;
protected FieldVector vector;
protected final CallBack repeatedCallBack;
- protected int valueCount;
protected long offsetAllocationSizeInBytes = INITIAL_VALUE_ALLOCATION * OFFSET_WIDTH;
private final String name;
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/BaseRepeatedValueViewVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/BaseRepeatedValueViewVector.java
index e6213316b5..fd7a4ff2c6 100644
--- a/vector/src/main/java/org/apache/arrow/vector/complex/BaseRepeatedValueViewVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/BaseRepeatedValueViewVector.java
@@ -52,7 +52,6 @@ public abstract class BaseRepeatedValueViewVector extends BaseValueVector
protected ArrowBuf sizeBuffer;
protected FieldVector vector;
protected final CallBack repeatedCallBack;
- protected int valueCount;
protected long offsetAllocationSizeInBytes = INITIAL_VALUE_ALLOCATION * OFFSET_WIDTH;
protected long sizeAllocationSizeInBytes = INITIAL_VALUE_ALLOCATION * SIZE_WIDTH;
private final String name;
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/FixedSizeListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/FixedSizeListVector.java
index c762eb5172..e3b4ab477f 100644
--- a/vector/src/main/java/org/apache/arrow/vector/complex/FixedSizeListVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/FixedSizeListVector.java
@@ -69,12 +69,10 @@ public static FixedSizeListVector empty(String name, int size, BufferAllocator a
}
private FieldVector vector;
- private ArrowBuf validityBuffer;
private final int listSize;
private Field field;
private UnionFixedSizeListReader reader;
- private int valueCount;
private int validityAllocationSizeInBytes;
/**
@@ -110,7 +108,8 @@ public FixedSizeListVector(
this.listSize = ((ArrowType.FixedSizeList) field.getFieldType().getType()).getListSize();
Preconditions.checkArgument(listSize >= 0, "list size must be non-negative");
this.valueCount = 0;
- this.validityAllocationSizeInBytes = getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION);
+ this.validityAllocationSizeInBytes =
+ BitVectorHelper.getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION);
}
@Override
@@ -189,7 +188,7 @@ public List getFieldBuffers() {
private void setReaderAndWriterIndex() {
validityBuffer.readerIndex(0);
- validityBuffer.writerIndex(getValidityBufferSizeFromCount(valueCount));
+ validityBuffer.writerIndex(BitVectorHelper.getValidityBufferSizeFromCount(valueCount));
}
/**
@@ -247,12 +246,10 @@ public boolean allocateNewSafe() {
return success;
}
- private void allocateValidityBuffer(final long size) {
- final int curSize = (int) size;
- validityBuffer = allocator.buffer(curSize);
- validityBuffer.readerIndex(0);
- validityAllocationSizeInBytes = curSize;
- validityBuffer.setZero(0, validityBuffer.capacity());
+ @Override
+ protected void allocateValidityBuffer(final long size) {
+ super.allocateValidityBuffer(size);
+ validityAllocationSizeInBytes = (int) size;
}
@Override
@@ -268,7 +265,8 @@ private void reallocValidityBuffer() {
if (validityAllocationSizeInBytes > 0) {
newAllocationSize = validityAllocationSizeInBytes;
} else {
- newAllocationSize = getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION) * 2L;
+ newAllocationSize =
+ BitVectorHelper.getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION) * 2L;
}
}
@@ -311,7 +309,7 @@ public UnionFixedSizeListWriter getWriter() {
@Override
public void setInitialCapacity(int numRecords) {
- validityAllocationSizeInBytes = getValidityBufferSizeFromCount(numRecords);
+ validityAllocationSizeInBytes = BitVectorHelper.getValidityBufferSizeFromCount(numRecords);
vector.setInitialCapacity(numRecords * listSize);
}
@@ -328,7 +326,7 @@ public int getBufferSize() {
if (getValueCount() == 0) {
return 0;
}
- return getValidityBufferSizeFromCount(valueCount) + vector.getBufferSize();
+ return BitVectorHelper.getValidityBufferSizeFromCount(valueCount) + vector.getBufferSize();
}
@Override
@@ -336,7 +334,7 @@ public int getBufferSizeFor(int valueCount) {
if (valueCount == 0) {
return 0;
}
- return getValidityBufferSizeFromCount(valueCount)
+ return BitVectorHelper.getValidityBufferSizeFromCount(valueCount)
+ vector.getBufferSizeFor(valueCount * listSize);
}
@@ -647,71 +645,6 @@ public void splitAndTransfer(int startIndex, int length) {
to.setValueCount(length);
}
- /*
- * transfer the validity.
- */
- private void splitAndTransferValidityBuffer(
- int startIndex, int length, FixedSizeListVector target) {
- int firstByteSource = BitVectorHelper.byteIndex(startIndex);
- int lastByteSource = BitVectorHelper.byteIndex(valueCount - 1);
- int byteSizeTarget = getValidityBufferSizeFromCount(length);
- int offset = startIndex % 8;
-
- if (length > 0) {
- if (offset == 0) {
- // slice
- if (target.validityBuffer != null) {
- target.validityBuffer.getReferenceManager().release();
- }
- target.validityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
- target.validityBuffer.getReferenceManager().retain(1);
- } else {
- /* Copy data
- * When the first bit starts from the middle of a byte (offset != 0),
- * copy data from src BitVector.
- * Each byte in the target is composed by a part in i-th byte,
- * another part in (i+1)-th byte.
- */
- target.allocateValidityBuffer(byteSizeTarget);
-
- for (int i = 0; i < byteSizeTarget - 1; i++) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(validityBuffer, firstByteSource + i, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- validityBuffer, firstByteSource + i + 1, offset);
-
- target.validityBuffer.setByte(i, (b1 + b2));
- }
-
- /* Copying the last piece is done in the following manner:
- * if the source vector has 1 or more bytes remaining, we copy
- * the last piece as a byte formed by shifting data
- * from the current byte and the next byte.
- *
- * if the source vector has no more bytes remaining
- * (we are at the last byte), we copy the last piece as a byte
- * by shifting data from the current byte.
- */
- if ((firstByteSource + byteSizeTarget - 1) < lastByteSource) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- validityBuffer, firstByteSource + byteSizeTarget, offset);
-
- target.validityBuffer.setByte(byteSizeTarget - 1, b1 + b2);
- } else {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- target.validityBuffer.setByte(byteSizeTarget - 1, b1);
- }
- }
- }
- }
-
@Override
public ValueVector getTo() {
return to;
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java
index ed075352c9..92dd3eaef7 100644
--- a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java
@@ -31,6 +31,7 @@
import org.apache.arrow.memory.util.ArrowBufPointer;
import org.apache.arrow.memory.util.ByteFunctionHelpers;
import org.apache.arrow.memory.util.CommonUtil;
+import org.apache.arrow.memory.util.LargeMemoryUtil;
import org.apache.arrow.memory.util.hash.ArrowBufHasher;
import org.apache.arrow.util.Preconditions;
import org.apache.arrow.vector.AddOrGetResult;
@@ -94,11 +95,9 @@ public static LargeListVector empty(String name, BufferAllocator allocator) {
protected ArrowBuf offsetBuffer;
protected FieldVector vector;
protected final CallBack callBack;
- protected int valueCount;
protected long offsetAllocationSizeInBytes = INITIAL_VALUE_ALLOCATION * OFFSET_WIDTH;
protected String defaultDataVectorName = DATA_VECTOR_NAME;
- protected ArrowBuf validityBuffer;
protected UnionLargeListReader reader;
private Field field;
private int validityAllocationSizeInBytes;
@@ -131,7 +130,8 @@ public LargeListVector(Field field, BufferAllocator allocator, CallBack callBack
this.field = field;
this.validityBuffer = allocator.getEmpty();
this.callBack = callBack;
- this.validityAllocationSizeInBytes = getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION);
+ this.validityAllocationSizeInBytes =
+ BitVectorHelper.getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION);
this.lastSet = -1;
this.offsetBuffer = allocator.getEmpty();
this.vector = vector == null ? DEFAULT_DATA_VECTOR : vector;
@@ -156,7 +156,7 @@ public void initializeChildrenFromFields(List children) {
@Override
public void setInitialCapacity(int numRecords) {
- validityAllocationSizeInBytes = getValidityBufferSizeFromCount(numRecords);
+ validityAllocationSizeInBytes = BitVectorHelper.getValidityBufferSizeFromCount(numRecords);
offsetAllocationSizeInBytes = (long) (numRecords + 1) * OFFSET_WIDTH;
if (vector instanceof BaseFixedWidthVector || vector instanceof BaseVariableWidthVector) {
vector.setInitialCapacity(numRecords * RepeatedValueVector.DEFAULT_REPEAT_PER_RECORD);
@@ -184,7 +184,7 @@ public void setInitialCapacity(int numRecords) {
*/
@Override
public void setInitialCapacity(int numRecords, double density) {
- validityAllocationSizeInBytes = getValidityBufferSizeFromCount(numRecords);
+ validityAllocationSizeInBytes = BitVectorHelper.getValidityBufferSizeFromCount(numRecords);
if ((numRecords * density) >= Integer.MAX_VALUE) {
throw new OversizedAllocationException("Requested amount of memory is more than max allowed");
}
@@ -309,11 +309,14 @@ private void setReaderAndWriterIndex() {
offsetBuffer.readerIndex(0);
if (valueCount == 0) {
validityBuffer.writerIndex(0);
- offsetBuffer.writerIndex(0);
} else {
- validityBuffer.writerIndex(getValidityBufferSizeFromCount(valueCount));
- offsetBuffer.writerIndex((valueCount + 1) * OFFSET_WIDTH);
+ validityBuffer.writerIndex(BitVectorHelper.getValidityBufferSizeFromCount(valueCount));
}
+ // 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].
+ offsetBuffer.writerIndex((long) (valueCount + 1) * OFFSET_WIDTH);
}
/**
@@ -374,12 +377,10 @@ public boolean allocateNewSafe() {
return success;
}
- private void allocateValidityBuffer(final long size) {
- final int curSize = (int) size;
- validityBuffer = allocator.buffer(curSize);
- validityBuffer.readerIndex(0);
- validityAllocationSizeInBytes = curSize;
- validityBuffer.setZero(0, validityBuffer.capacity());
+ @Override
+ protected void allocateValidityBuffer(final long size) {
+ super.allocateValidityBuffer(size);
+ validityAllocationSizeInBytes = (int) size;
}
protected ArrowBuf allocateOffsetBuffer(final long size) {
@@ -442,7 +443,8 @@ private void reallocValidityBuffer() {
if (validityAllocationSizeInBytes > 0) {
newAllocationSize = validityAllocationSizeInBytes;
} else {
- newAllocationSize = getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION) * 2L;
+ newAllocationSize =
+ BitVectorHelper.getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION) * 2L;
}
}
newAllocationSize = CommonUtil.nextPowerOfTwo(newAllocationSize);
@@ -692,71 +694,6 @@ public void splitAndTransfer(int startIndex, int length) {
to.setValueCount(length);
}
- /*
- * transfer the validity.
- */
- private void splitAndTransferValidityBuffer(
- int startIndex, int length, LargeListVector target) {
- int firstByteSource = BitVectorHelper.byteIndex(startIndex);
- int lastByteSource = BitVectorHelper.byteIndex(valueCount - 1);
- int byteSizeTarget = getValidityBufferSizeFromCount(length);
- int offset = startIndex % 8;
-
- if (length > 0) {
- if (offset == 0) {
- // slice
- if (target.validityBuffer != null) {
- target.validityBuffer.getReferenceManager().release();
- }
- target.validityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
- target.validityBuffer.getReferenceManager().retain(1);
- } else {
- /* Copy data
- * When the first bit starts from the middle of a byte (offset != 0),
- * copy data from src BitVector.
- * Each byte in the target is composed by a part in i-th byte,
- * another part in (i+1)-th byte.
- */
- target.allocateValidityBuffer(byteSizeTarget);
-
- for (int i = 0; i < byteSizeTarget - 1; i++) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(validityBuffer, firstByteSource + i, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- validityBuffer, firstByteSource + i + 1, offset);
-
- target.validityBuffer.setByte(i, (b1 + b2));
- }
-
- /* Copying the last piece is done in the following manner:
- * if the source vector has 1 or more bytes remaining, we copy
- * the last piece as a byte formed by shifting data
- * from the current byte and the next byte.
- *
- * if the source vector has no more bytes remaining
- * (we are at the last byte), we copy the last piece as a byte
- * by shifting data from the current byte.
- */
- if ((firstByteSource + byteSizeTarget - 1) < lastByteSource) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- validityBuffer, firstByteSource + byteSizeTarget, offset);
-
- target.validityBuffer.setByte(byteSizeTarget - 1, b1 + b2);
- } else {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- target.validityBuffer.setByte(byteSizeTarget - 1, b1);
- }
- }
- }
- }
-
@Override
public ValueVector getTo() {
return to;
@@ -821,7 +758,7 @@ public int getBufferSize() {
return 0;
}
final int offsetBufferSize = (valueCount + 1) * OFFSET_WIDTH;
- final int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
+ final int validityBufferSize = BitVectorHelper.getValidityBufferSizeFromCount(valueCount);
return offsetBufferSize + validityBufferSize + vector.getBufferSize();
}
@@ -830,7 +767,7 @@ public int getBufferSizeFor(int valueCount) {
if (valueCount == 0) {
return 0;
}
- final int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
+ final int validityBufferSize = BitVectorHelper.getValidityBufferSizeFromCount(valueCount);
long innerVectorValueCount = offsetBuffer.getLong((long) valueCount * OFFSET_WIDTH);
return ((valueCount + 1) * OFFSET_WIDTH)
@@ -928,10 +865,11 @@ public List> getObject(int index) {
if (isSet(index) == 0) {
return null;
}
- final List vals = new JsonStringArrayList<>();
final long start = offsetBuffer.getLong((long) index * OFFSET_WIDTH);
final long end = offsetBuffer.getLong(((long) index + 1L) * OFFSET_WIDTH);
final ValueVector vv = getDataVector();
+ final List vals =
+ new JsonStringArrayList<>(LargeMemoryUtil.checkedCastToInt(end - start));
for (long i = start; i < end; i++) {
vals.add(vv.getObject(checkedCastToInt(i)));
}
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListViewVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListViewVector.java
index 84c6f03edb..2da7eb057e 100644
--- a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListViewVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListViewVector.java
@@ -77,7 +77,6 @@
public class LargeListViewVector extends BaseLargeRepeatedValueViewVector
implements PromotableVector, ValueIterableVector> {
- protected ArrowBuf validityBuffer;
protected UnionLargeListViewReader reader;
private CallBack callBack;
protected Field field;
@@ -113,7 +112,8 @@ public LargeListViewVector(Field field, BufferAllocator allocator, CallBack call
this.validityBuffer = allocator.getEmpty();
this.field = field;
this.callBack = callBack;
- this.validityAllocationSizeInBytes = getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION);
+ this.validityAllocationSizeInBytes =
+ BitVectorHelper.getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION);
}
@Override
@@ -134,7 +134,7 @@ public void initializeChildrenFromFields(List children) {
@Override
public void setInitialCapacity(int numRecords) {
- validityAllocationSizeInBytes = getValidityBufferSizeFromCount(numRecords);
+ validityAllocationSizeInBytes = BitVectorHelper.getValidityBufferSizeFromCount(numRecords);
super.setInitialCapacity(numRecords);
}
@@ -157,7 +157,7 @@ public void setInitialCapacity(int numRecords) {
*/
@Override
public void setInitialCapacity(int numRecords, double density) {
- validityAllocationSizeInBytes = getValidityBufferSizeFromCount(numRecords);
+ validityAllocationSizeInBytes = BitVectorHelper.getValidityBufferSizeFromCount(numRecords);
super.setInitialCapacity(numRecords, density);
}
@@ -176,7 +176,7 @@ public void setInitialCapacity(int numRecords, double density) {
*/
@Override
public void setInitialTotalCapacity(int numRecords, int totalNumberOfElements) {
- validityAllocationSizeInBytes = getValidityBufferSizeFromCount(numRecords);
+ validityAllocationSizeInBytes = BitVectorHelper.getValidityBufferSizeFromCount(numRecords);
super.setInitialTotalCapacity(numRecords, totalNumberOfElements);
}
@@ -226,7 +226,7 @@ private void setReaderAndWriterIndex() {
offsetBuffer.writerIndex(0);
sizeBuffer.writerIndex(0);
} else {
- validityBuffer.writerIndex(getValidityBufferSizeFromCount(valueCount));
+ validityBuffer.writerIndex(BitVectorHelper.getValidityBufferSizeFromCount(valueCount));
offsetBuffer.writerIndex((long) valueCount * OFFSET_WIDTH);
sizeBuffer.writerIndex((long) valueCount * SIZE_WIDTH);
}
@@ -284,12 +284,10 @@ public boolean allocateNewSafe() {
return success;
}
+ @Override
protected void allocateValidityBuffer(final long size) {
- final int curSize = (int) size;
- validityBuffer = allocator.buffer(curSize);
- validityBuffer.readerIndex(0);
- validityAllocationSizeInBytes = curSize;
- validityBuffer.setZero(0, validityBuffer.capacity());
+ super.allocateValidityBuffer(size);
+ validityAllocationSizeInBytes = (int) size;
}
@Override
@@ -323,7 +321,8 @@ private long getNewAllocationSize(int currentBufferCapacity) {
if (validityAllocationSizeInBytes > 0) {
newAllocationSize = validityAllocationSizeInBytes;
} else {
- newAllocationSize = getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION) * 2L;
+ newAllocationSize =
+ BitVectorHelper.getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION) * 2L;
}
}
newAllocationSize = CommonUtil.nextPowerOfTwo(newAllocationSize);
@@ -529,71 +528,6 @@ public void splitAndTransfer(int startIndex, int length) {
}
}
- /*
- * transfer the validity.
- */
- private void splitAndTransferValidityBuffer(
- int startIndex, int length, LargeListViewVector target) {
- int firstByteSource = BitVectorHelper.byteIndex(startIndex);
- int lastByteSource = BitVectorHelper.byteIndex(valueCount - 1);
- int byteSizeTarget = getValidityBufferSizeFromCount(length);
- int offset = startIndex % 8;
-
- if (length > 0) {
- if (offset == 0) {
- // slice
- if (target.validityBuffer != null) {
- target.validityBuffer.getReferenceManager().release();
- }
- target.validityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
- target.validityBuffer.getReferenceManager().retain(1);
- } else {
- /* Copy data
- * When the first bit starts from the middle of a byte (offset != 0),
- * copy data from src BitVector.
- * Each byte in the target is composed by a part in i-th byte,
- * another part in (i+1)-th byte.
- */
- target.allocateValidityBuffer(byteSizeTarget);
-
- for (int i = 0; i < byteSizeTarget - 1; i++) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(validityBuffer, firstByteSource + i, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- validityBuffer, firstByteSource + i + 1, offset);
-
- target.validityBuffer.setByte(i, (b1 + b2));
- }
-
- /* Copying the last piece is done in the following manner:
- * if the source vector has 1 or more bytes remaining, we copy
- * the last piece as a byte formed by shifting data
- * from the current byte and the next byte.
- *
- * if the source vector has no more bytes remaining
- * (we are at the last byte), we copy the last piece as a byte
- * by shifting data from the current byte.
- */
- if ((firstByteSource + byteSizeTarget - 1) < lastByteSource) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- validityBuffer, firstByteSource + byteSizeTarget, offset);
-
- target.validityBuffer.setByte(byteSizeTarget - 1, b1 + b2);
- } else {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- target.validityBuffer.setByte(byteSizeTarget - 1, b1);
- }
- }
- }
- }
-
@Override
public ValueVector getTo() {
return to;
@@ -629,7 +563,7 @@ public int getBufferSize() {
}
final int offsetBufferSize = valueCount * OFFSET_WIDTH;
final int sizeBufferSize = valueCount * SIZE_WIDTH;
- final int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
+ final int validityBufferSize = BitVectorHelper.getValidityBufferSizeFromCount(valueCount);
return offsetBufferSize + sizeBufferSize + validityBufferSize + vector.getBufferSize();
}
@@ -644,7 +578,7 @@ public int getBufferSizeFor(int valueCount) {
if (valueCount == 0) {
return 0;
}
- final int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
+ final int validityBufferSize = BitVectorHelper.getValidityBufferSizeFromCount(valueCount);
return super.getBufferSizeFor(valueCount) + validityBufferSize;
}
@@ -738,10 +672,10 @@ public List> getObject(int index) {
if (isSet(index) == 0) {
return null;
}
- final List vals = new JsonStringArrayList<>();
final int start = offsetBuffer.getInt(index * OFFSET_WIDTH);
final int end = start + sizeBuffer.getInt((index) * SIZE_WIDTH);
final ValueVector vv = getDataVector();
+ final List vals = new JsonStringArrayList<>(end - start);
for (int i = start; i < end; i++) {
vals.add(vv.getObject(checkedCastToInt(i)));
}
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java
index 3daeb6d77b..6c3993df63 100644
--- a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java
@@ -74,7 +74,6 @@ public static ListVector empty(String name, BufferAllocator allocator) {
return new ListVector(name, allocator, FieldType.nullable(ArrowType.List.INSTANCE), null);
}
- protected ArrowBuf validityBuffer;
protected UnionListReader reader;
private CallBack callBack;
protected Field field;
@@ -108,7 +107,8 @@ public ListVector(Field field, BufferAllocator allocator, CallBack callBack) {
this.validityBuffer = allocator.getEmpty();
this.field = field;
this.callBack = callBack;
- this.validityAllocationSizeInBytes = getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION);
+ this.validityAllocationSizeInBytes =
+ BitVectorHelper.getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION);
this.lastSet = -1;
}
@@ -130,7 +130,7 @@ public void initializeChildrenFromFields(List children) {
@Override
public void setInitialCapacity(int numRecords) {
- validityAllocationSizeInBytes = getValidityBufferSizeFromCount(numRecords);
+ validityAllocationSizeInBytes = BitVectorHelper.getValidityBufferSizeFromCount(numRecords);
super.setInitialCapacity(numRecords);
}
@@ -153,7 +153,7 @@ public void setInitialCapacity(int numRecords) {
*/
@Override
public void setInitialCapacity(int numRecords, double density) {
- validityAllocationSizeInBytes = getValidityBufferSizeFromCount(numRecords);
+ validityAllocationSizeInBytes = BitVectorHelper.getValidityBufferSizeFromCount(numRecords);
super.setInitialCapacity(numRecords, density);
}
@@ -172,7 +172,7 @@ public void setInitialCapacity(int numRecords, double density) {
*/
@Override
public void setInitialTotalCapacity(int numRecords, int totalNumberOfElements) {
- validityAllocationSizeInBytes = getValidityBufferSizeFromCount(numRecords);
+ validityAllocationSizeInBytes = BitVectorHelper.getValidityBufferSizeFromCount(numRecords);
super.setInitialTotalCapacity(numRecords, totalNumberOfElements);
}
@@ -267,11 +267,14 @@ private void setReaderAndWriterIndex() {
offsetBuffer.readerIndex(0);
if (valueCount == 0) {
validityBuffer.writerIndex(0);
- offsetBuffer.writerIndex(0);
} else {
- validityBuffer.writerIndex(getValidityBufferSizeFromCount(valueCount));
- offsetBuffer.writerIndex((valueCount + 1) * OFFSET_WIDTH);
+ validityBuffer.writerIndex(BitVectorHelper.getValidityBufferSizeFromCount(valueCount));
}
+ // 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].
+ offsetBuffer.writerIndex((long) (valueCount + 1) * OFFSET_WIDTH);
}
/**
@@ -323,12 +326,10 @@ public boolean allocateNewSafe() {
return success;
}
+ @Override
protected void allocateValidityBuffer(final long size) {
- final int curSize = (int) size;
- validityBuffer = allocator.buffer(curSize);
- validityBuffer.readerIndex(0);
- validityAllocationSizeInBytes = curSize;
- validityBuffer.setZero(0, validityBuffer.capacity());
+ super.allocateValidityBuffer(size);
+ validityAllocationSizeInBytes = (int) size;
}
/**
@@ -366,7 +367,8 @@ private long getNewAllocationSize(int currentBufferCapacity) {
if (validityAllocationSizeInBytes > 0) {
newAllocationSize = validityAllocationSizeInBytes;
} else {
- newAllocationSize = getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION) * 2L;
+ newAllocationSize =
+ BitVectorHelper.getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION) * 2L;
}
}
newAllocationSize = CommonUtil.nextPowerOfTwo(newAllocationSize);
@@ -573,70 +575,6 @@ public void splitAndTransfer(int startIndex, int length) {
}
}
- /*
- * transfer the validity.
- */
- private void splitAndTransferValidityBuffer(int startIndex, int length, ListVector target) {
- int firstByteSource = BitVectorHelper.byteIndex(startIndex);
- int lastByteSource = BitVectorHelper.byteIndex(valueCount - 1);
- int byteSizeTarget = getValidityBufferSizeFromCount(length);
- int offset = startIndex % 8;
-
- if (length > 0) {
- if (offset == 0) {
- // slice
- if (target.validityBuffer != null) {
- target.validityBuffer.getReferenceManager().release();
- }
- target.validityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
- target.validityBuffer.getReferenceManager().retain(1);
- } else {
- /* Copy data
- * When the first bit starts from the middle of a byte (offset != 0),
- * copy data from src BitVector.
- * Each byte in the target is composed by a part in i-th byte,
- * another part in (i+1)-th byte.
- */
- target.allocateValidityBuffer(byteSizeTarget);
-
- for (int i = 0; i < byteSizeTarget - 1; i++) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(validityBuffer, firstByteSource + i, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- validityBuffer, firstByteSource + i + 1, offset);
-
- target.validityBuffer.setByte(i, (b1 + b2));
- }
-
- /* Copying the last piece is done in the following manner:
- * if the source vector has 1 or more bytes remaining, we copy
- * the last piece as a byte formed by shifting data
- * from the current byte and the next byte.
- *
- * if the source vector has no more bytes remaining
- * (we are at the last byte), we copy the last piece as a byte
- * by shifting data from the current byte.
- */
- if ((firstByteSource + byteSizeTarget - 1) < lastByteSource) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- validityBuffer, firstByteSource + byteSizeTarget, offset);
-
- target.validityBuffer.setByte(byteSizeTarget - 1, b1 + b2);
- } else {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- target.validityBuffer.setByte(byteSizeTarget - 1, b1);
- }
- }
- }
- }
-
@Override
public ValueVector getTo() {
return to;
@@ -678,7 +616,7 @@ public int getBufferSize() {
return 0;
}
final int offsetBufferSize = (valueCount + 1) * OFFSET_WIDTH;
- final int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
+ final int validityBufferSize = BitVectorHelper.getValidityBufferSizeFromCount(valueCount);
return offsetBufferSize + validityBufferSize + vector.getBufferSize();
}
@@ -687,7 +625,7 @@ public int getBufferSizeFor(int valueCount) {
if (valueCount == 0) {
return 0;
}
- final int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
+ final int validityBufferSize = BitVectorHelper.getValidityBufferSizeFromCount(valueCount);
return super.getBufferSizeFor(valueCount) + validityBufferSize;
}
@@ -784,10 +722,10 @@ public List> getObject(int index) {
if (isSet(index) == 0) {
return null;
}
- final List vals = new JsonStringArrayList<>();
final int start = offsetBuffer.getInt(index * OFFSET_WIDTH);
final int end = offsetBuffer.getInt((index + 1) * OFFSET_WIDTH);
final ValueVector vv = getDataVector();
+ final List vals = new JsonStringArrayList<>(end - start);
for (int i = start; i < end; i++) {
vals.add(vv.getObject(i));
}
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/ListViewVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/ListViewVector.java
index 9b4e6b4c0c..d41f61e291 100644
--- a/vector/src/main/java/org/apache/arrow/vector/complex/ListViewVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/ListViewVector.java
@@ -76,7 +76,6 @@
public class ListViewVector extends BaseRepeatedValueViewVector
implements PromotableVector, ValueIterableVector> {
- protected ArrowBuf validityBuffer;
protected UnionListViewReader reader;
private CallBack callBack;
protected Field field;
@@ -112,7 +111,8 @@ public ListViewVector(Field field, BufferAllocator allocator, CallBack callBack)
this.validityBuffer = allocator.getEmpty();
this.field = field;
this.callBack = callBack;
- this.validityAllocationSizeInBytes = getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION);
+ this.validityAllocationSizeInBytes =
+ BitVectorHelper.getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION);
}
@Override
@@ -133,7 +133,7 @@ public void initializeChildrenFromFields(List children) {
@Override
public void setInitialCapacity(int numRecords) {
- validityAllocationSizeInBytes = getValidityBufferSizeFromCount(numRecords);
+ validityAllocationSizeInBytes = BitVectorHelper.getValidityBufferSizeFromCount(numRecords);
super.setInitialCapacity(numRecords);
}
@@ -156,7 +156,7 @@ public void setInitialCapacity(int numRecords) {
*/
@Override
public void setInitialCapacity(int numRecords, double density) {
- validityAllocationSizeInBytes = getValidityBufferSizeFromCount(numRecords);
+ validityAllocationSizeInBytes = BitVectorHelper.getValidityBufferSizeFromCount(numRecords);
super.setInitialCapacity(numRecords, density);
}
@@ -175,7 +175,7 @@ public void setInitialCapacity(int numRecords, double density) {
*/
@Override
public void setInitialTotalCapacity(int numRecords, int totalNumberOfElements) {
- validityAllocationSizeInBytes = getValidityBufferSizeFromCount(numRecords);
+ validityAllocationSizeInBytes = BitVectorHelper.getValidityBufferSizeFromCount(numRecords);
super.setInitialTotalCapacity(numRecords, totalNumberOfElements);
}
@@ -225,9 +225,9 @@ private void setReaderAndWriterIndex() {
offsetBuffer.writerIndex(0);
sizeBuffer.writerIndex(0);
} else {
- validityBuffer.writerIndex(getValidityBufferSizeFromCount(valueCount));
- offsetBuffer.writerIndex(valueCount * OFFSET_WIDTH);
- sizeBuffer.writerIndex(valueCount * SIZE_WIDTH);
+ validityBuffer.writerIndex(BitVectorHelper.getValidityBufferSizeFromCount(valueCount));
+ offsetBuffer.writerIndex((long) valueCount * OFFSET_WIDTH);
+ sizeBuffer.writerIndex((long) valueCount * SIZE_WIDTH);
}
}
@@ -283,12 +283,10 @@ public boolean allocateNewSafe() {
return success;
}
+ @Override
protected void allocateValidityBuffer(final long size) {
- final int curSize = (int) size;
- validityBuffer = allocator.buffer(curSize);
- validityBuffer.readerIndex(0);
- validityAllocationSizeInBytes = curSize;
- validityBuffer.setZero(0, validityBuffer.capacity());
+ super.allocateValidityBuffer(size);
+ validityAllocationSizeInBytes = (int) size;
}
@Override
@@ -322,7 +320,8 @@ private long getNewAllocationSize(int currentBufferCapacity) {
if (validityAllocationSizeInBytes > 0) {
newAllocationSize = validityAllocationSizeInBytes;
} else {
- newAllocationSize = getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION) * 2L;
+ newAllocationSize =
+ BitVectorHelper.getValidityBufferSizeFromCount(INITIAL_VALUE_ALLOCATION) * 2L;
}
}
newAllocationSize = CommonUtil.nextPowerOfTwo(newAllocationSize);
@@ -446,14 +445,22 @@ public int hashCode(int index, ArrowBufHasher hasher) {
return ArrowBufPointer.NULL_HASH_CODE;
}
int hash = 0;
- final int start = offsetBuffer.getInt(index * OFFSET_WIDTH);
- final int end = sizeBuffer.getInt(index * OFFSET_WIDTH);
+ final int start = getElementStartIndex(index);
+ final int end = getElementEndIndex(index);
for (int i = start; i < end; i++) {
hash = ByteFunctionHelpers.combineHash(hash, vector.hashCode(i, hasher));
}
return hash;
}
+ private void setElementOffsetBuffer(int index, int value) {
+ offsetBuffer.setInt((long) index * OFFSET_WIDTH, value);
+ }
+
+ private void setElementSizeBuffer(int index, int value) {
+ sizeBuffer.setInt((long) index * SIZE_WIDTH, value);
+ }
+
private class TransferImpl implements TransferPair {
ListViewVector to;
@@ -499,7 +506,6 @@ public void splitAndTransfer(int startIndex, int length) {
valueCount);
to.clear();
if (length > 0) {
- final int startPoint = offsetBuffer.getInt((long) startIndex * OFFSET_WIDTH);
// we have to scan by index since there are out-of-order offsets
to.offsetBuffer = to.allocateBuffers((long) length * OFFSET_WIDTH);
to.sizeBuffer = to.allocateBuffers((long) length * SIZE_WIDTH);
@@ -508,9 +514,9 @@ public void splitAndTransfer(int startIndex, int length) {
int maxOffsetAndSizeSum = -1;
int minOffsetValue = -1;
for (int i = 0; i < length; i++) {
- final int offsetValue = offsetBuffer.getInt((long) (startIndex + i) * OFFSET_WIDTH);
- final int sizeValue = sizeBuffer.getInt((long) (startIndex + i) * SIZE_WIDTH);
- to.sizeBuffer.setInt((long) i * SIZE_WIDTH, sizeValue);
+ final int offsetValue = getElementStartIndex(startIndex + i);
+ final int sizeValue = getElementSize(startIndex + i);
+ to.setElementSizeBuffer(i, sizeValue);
if (maxOffsetAndSizeSum < offsetValue + sizeValue) {
maxOffsetAndSizeSum = offsetValue + sizeValue;
}
@@ -521,9 +527,9 @@ public void splitAndTransfer(int startIndex, int length) {
/* splitAndTransfer the offset buffer */
for (int i = 0; i < length; i++) {
- final int offsetValue = offsetBuffer.getInt((long) (startIndex + i) * OFFSET_WIDTH);
+ final int offsetValue = getElementStartIndex(startIndex + i);
final int relativeOffset = offsetValue - minOffsetValue;
- to.offsetBuffer.setInt((long) i * OFFSET_WIDTH, relativeOffset);
+ to.setElementOffsetBuffer(i, relativeOffset);
}
/* splitAndTransfer the validity buffer */
@@ -536,70 +542,6 @@ public void splitAndTransfer(int startIndex, int length) {
}
}
- /*
- * transfer the validity.
- */
- private void splitAndTransferValidityBuffer(int startIndex, int length, ListViewVector target) {
- int firstByteSource = BitVectorHelper.byteIndex(startIndex);
- int lastByteSource = BitVectorHelper.byteIndex(valueCount - 1);
- int byteSizeTarget = getValidityBufferSizeFromCount(length);
- int offset = startIndex % 8;
-
- if (length > 0) {
- if (offset == 0) {
- // slice
- if (target.validityBuffer != null) {
- target.validityBuffer.getReferenceManager().release();
- }
- target.validityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
- target.validityBuffer.getReferenceManager().retain(1);
- } else {
- /* Copy data
- * When the first bit starts from the middle of a byte (offset != 0),
- * copy data from src BitVector.
- * Each byte in the target is composed by a part in i-th byte,
- * another part in (i+1)-th byte.
- */
- target.allocateValidityBuffer(byteSizeTarget);
-
- for (int i = 0; i < byteSizeTarget - 1; i++) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(validityBuffer, firstByteSource + i, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- validityBuffer, firstByteSource + i + 1, offset);
-
- target.validityBuffer.setByte(i, (b1 + b2));
- }
-
- /* Copying the last piece is done in the following manner:
- * if the source vector has 1 or more bytes remaining, we copy
- * the last piece as a byte formed by shifting data
- * from the current byte and the next byte.
- *
- * if the source vector has no more bytes remaining
- * (we are at the last byte), we copy the last piece as a byte
- * by shifting data from the current byte.
- */
- if ((firstByteSource + byteSizeTarget - 1) < lastByteSource) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- validityBuffer, firstByteSource + byteSizeTarget, offset);
-
- target.validityBuffer.setByte(byteSizeTarget - 1, b1 + b2);
- } else {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- target.validityBuffer.setByte(byteSizeTarget - 1, b1);
- }
- }
- }
- }
-
@Override
public ValueVector getTo() {
return to;
@@ -634,7 +576,7 @@ public int getBufferSize() {
}
final int offsetBufferSize = valueCount * OFFSET_WIDTH;
final int sizeBufferSize = valueCount * SIZE_WIDTH;
- final int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
+ final int validityBufferSize = BitVectorHelper.getValidityBufferSizeFromCount(valueCount);
return offsetBufferSize + sizeBufferSize + validityBufferSize + vector.getBufferSize();
}
@@ -649,7 +591,7 @@ public int getBufferSizeFor(int valueCount) {
if (valueCount == 0) {
return 0;
}
- final int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
+ final int validityBufferSize = BitVectorHelper.getValidityBufferSizeFromCount(valueCount);
return super.getBufferSizeFor(valueCount) + validityBufferSize;
}
@@ -743,10 +685,10 @@ public List> getObject(int index) {
if (isSet(index) == 0) {
return null;
}
- final List vals = new JsonStringArrayList<>();
- final int start = offsetBuffer.getInt(index * OFFSET_WIDTH);
- final int end = start + sizeBuffer.getInt((index) * SIZE_WIDTH);
+ final int start = getElementStartIndex(index);
+ final int end = getElementEndIndex(index);
final ValueVector vv = getDataVector();
+ final List vals = new JsonStringArrayList<>(end - start);
for (int i = start; i < end; i++) {
vals.add(vv.getObject(i));
}
@@ -776,7 +718,7 @@ public boolean isEmpty(int index) {
if (isNull(index)) {
return true;
} else {
- return sizeBuffer.getInt(index * SIZE_WIDTH) == 0;
+ return getElementSize(index) == 0;
}
}
@@ -787,10 +729,7 @@ public boolean isEmpty(int index) {
* @return 1 if element at given index is not null, 0 otherwise
*/
public int isSet(int index) {
- final int byteIndex = index >> 3;
- final byte b = validityBuffer.getByte(byteIndex);
- final int bitIndex = index & 7;
- return (b >> bitIndex) & 0x01;
+ return BitVectorHelper.get(validityBuffer, index);
}
/**
@@ -840,8 +779,8 @@ public void setNull(int index) {
reallocValidityAndSizeAndOffsetBuffers();
}
- offsetBuffer.setInt(index * OFFSET_WIDTH, 0);
- sizeBuffer.setInt(index * SIZE_WIDTH, 0);
+ setElementOffsetBuffer(index, 0);
+ setElementSizeBuffer(index, 0);
BitVectorHelper.unsetBit(validityBuffer, index);
}
@@ -859,11 +798,11 @@ public int startNewValue(int index) {
if (index > 0) {
final int prevOffset = getMaxViewEndChildVectorByIndex(index);
- offsetBuffer.setInt(index * OFFSET_WIDTH, prevOffset);
+ setElementOffsetBuffer(index, prevOffset);
}
BitVectorHelper.setBit(validityBuffer, index);
- return offsetBuffer.getInt(index * OFFSET_WIDTH);
+ return getElementStartIndex(index);
}
/**
@@ -901,9 +840,9 @@ private void validateInvariants(int offset, int size) {
* @param value value to set
*/
public void setOffset(int index, int value) {
- validateInvariants(value, sizeBuffer.getInt(index * SIZE_WIDTH));
+ validateInvariants(value, getElementSize(index));
- offsetBuffer.setInt(index * OFFSET_WIDTH, value);
+ setElementOffsetBuffer(index, value);
}
/**
@@ -913,9 +852,9 @@ public void setOffset(int index, int value) {
* @param value value to set
*/
public void setSize(int index, int value) {
- validateInvariants(offsetBuffer.getInt(index * SIZE_WIDTH), value);
+ validateInvariants(getElementStartIndex(index), value);
- sizeBuffer.setInt(index * SIZE_WIDTH, value);
+ setElementSizeBuffer(index, value);
}
/**
@@ -951,12 +890,16 @@ public void setValueCount(int valueCount) {
@Override
public int getElementStartIndex(int index) {
- return offsetBuffer.getInt(index * OFFSET_WIDTH);
+ return offsetBuffer.getInt((long) index * OFFSET_WIDTH);
+ }
+
+ private int getElementSize(int index) {
+ return sizeBuffer.getInt((long) index * SIZE_WIDTH);
}
@Override
public int getElementEndIndex(int index) {
- return sizeBuffer.getInt(index * OFFSET_WIDTH);
+ return getElementStartIndex(index) + getElementSize(index);
}
@Override
@@ -1013,8 +956,8 @@ public double getDensity() {
@Override
public void validate() {
for (int i = 0; i < valueCount; i++) {
- final int offset = offsetBuffer.getInt(i * OFFSET_WIDTH);
- final int size = sizeBuffer.getInt(i * SIZE_WIDTH);
+ final int offset = getElementStartIndex(i);
+ final int size = getElementSize(i);
validateInvariants(offset, size);
}
}
@@ -1026,6 +969,6 @@ public void validate() {
* @param size number of elements in the list that was written
*/
public void endValue(int index, int size) {
- sizeBuffer.setInt(index * SIZE_WIDTH, size);
+ setElementSizeBuffer(index, size);
}
}
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/MapVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/MapVector.java
index 23cda8401b..3f98322ba9 100644
--- a/vector/src/main/java/org/apache/arrow/vector/complex/MapVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/MapVector.java
@@ -22,7 +22,6 @@
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.util.Preconditions;
import org.apache.arrow.vector.AddOrGetResult;
-import org.apache.arrow.vector.BitVectorHelper;
import org.apache.arrow.vector.FieldVector;
import org.apache.arrow.vector.ValueVector;
import org.apache.arrow.vector.ZeroVector;
@@ -232,70 +231,6 @@ public void splitAndTransfer(int startIndex, int length) {
}
}
- /*
- * transfer the validity.
- */
- private void splitAndTransferValidityBuffer(int startIndex, int length, MapVector target) {
- int firstByteSource = BitVectorHelper.byteIndex(startIndex);
- int lastByteSource = BitVectorHelper.byteIndex(valueCount - 1);
- int byteSizeTarget = getValidityBufferSizeFromCount(length);
- int offset = startIndex % 8;
-
- if (length > 0) {
- if (offset == 0) {
- // slice
- if (target.validityBuffer != null) {
- target.validityBuffer.getReferenceManager().release();
- }
- target.validityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
- target.validityBuffer.getReferenceManager().retain(1);
- } else {
- /* Copy data
- * When the first bit starts from the middle of a byte (offset != 0),
- * copy data from src BitVector.
- * Each byte in the target is composed by a part in i-th byte,
- * another part in (i+1)-th byte.
- */
- target.allocateValidityBuffer(byteSizeTarget);
-
- for (int i = 0; i < byteSizeTarget - 1; i++) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(validityBuffer, firstByteSource + i, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- validityBuffer, firstByteSource + i + 1, offset);
-
- target.validityBuffer.setByte(i, (b1 + b2));
- }
-
- /* Copying the last piece is done in the following manner:
- * if the source vector has 1 or more bytes remaining, we copy
- * the last piece as a byte formed by shifting data
- * from the current byte and the next byte.
- *
- * if the source vector has no more bytes remaining
- * (we are at the last byte), we copy the last piece as a byte
- * by shifting data from the current byte.
- */
- if ((firstByteSource + byteSizeTarget - 1) < lastByteSource) {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- byte b2 =
- BitVectorHelper.getBitsFromNextByte(
- validityBuffer, firstByteSource + byteSizeTarget, offset);
-
- target.validityBuffer.setByte(byteSizeTarget - 1, b1 + b2);
- } else {
- byte b1 =
- BitVectorHelper.getBitsFromCurrentByte(
- validityBuffer, firstByteSource + byteSizeTarget - 1, offset);
- target.validityBuffer.setByte(byteSizeTarget - 1, b1);
- }
- }
- }
- }
-
@Override
public ValueVector getTo() {
return to;
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/RunEndEncodedVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/RunEndEncodedVector.java
index 1bb9a3d6c0..b83e13449a 100644
--- a/vector/src/main/java/org/apache/arrow/vector/complex/RunEndEncodedVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/RunEndEncodedVector.java
@@ -28,6 +28,7 @@
import org.apache.arrow.memory.OutOfMemoryException;
import org.apache.arrow.memory.util.ByteFunctionHelpers;
import org.apache.arrow.memory.util.hash.ArrowBufHasher;
+import org.apache.arrow.util.Preconditions;
import org.apache.arrow.vector.BaseIntVector;
import org.apache.arrow.vector.BaseValueVector;
import org.apache.arrow.vector.BigIntVector;
@@ -820,4 +821,101 @@ static int getPhysicalIndex(FieldVector runEndVector, int logicalIndex) {
return result;
}
+
+ public static class RangeIterator {
+
+ private final RunEndEncodedVector runEndEncodedVector;
+ private final int rangeEnd;
+ private int runIndex;
+ private int runEnd;
+ private int logicalPos;
+
+ /**
+ * Constructs a new RangeIterator for iterating over a range of values in a RunEndEncodedVector.
+ *
+ * @param runEndEncodedVector The vector to iterate over
+ * @param startIndex The logical start index of the range (inclusive)
+ * @param length The number of values to include in the range
+ * @throws IllegalArgumentException if startIndex is negative or (startIndex + length) exceeds
+ * vector bounds
+ */
+ public RangeIterator(RunEndEncodedVector runEndEncodedVector, int startIndex, int length) {
+ int rangeEnd = startIndex + length;
+ Preconditions.checkArgument(
+ startIndex >= 0, "startIndex %s must be non negative.", startIndex);
+ Preconditions.checkArgument(
+ rangeEnd <= runEndEncodedVector.getValueCount(),
+ "(startIndex + length) %s out of range[0, %s].",
+ rangeEnd,
+ runEndEncodedVector.getValueCount());
+
+ this.rangeEnd = rangeEnd;
+ this.runEndEncodedVector = runEndEncodedVector;
+ this.runIndex = runEndEncodedVector.getPhysicalIndex(startIndex) - 1;
+ this.runEnd = startIndex;
+ this.logicalPos = -1;
+ }
+
+ /**
+ * Advances to the next run in the range.
+ *
+ * @return true if there is another run available, false if iteration has completed
+ */
+ public boolean nextRun() {
+ logicalPos = runEnd;
+ if (logicalPos >= rangeEnd) {
+ return false;
+ }
+ updateRun();
+ return true;
+ }
+
+ private void updateRun() {
+ runIndex++;
+ runEnd = (int) ((BaseIntVector) runEndEncodedVector.runEndsVector).getValueAsLong(runIndex);
+ }
+
+ /**
+ * Advances to the next value in the range.
+ *
+ * @return true if there is another value available, false if iteration has completed
+ */
+ public boolean nextValue() {
+ logicalPos++;
+ if (logicalPos >= rangeEnd) {
+ return false;
+ }
+ if (logicalPos == runEnd) {
+ updateRun();
+ }
+ return true;
+ }
+
+ /**
+ * Gets the current run index (physical position in the run-ends vector).
+ *
+ * @return the current run index
+ */
+ public int getRunIndex() {
+ return runIndex;
+ }
+
+ /**
+ * Gets the length of the current run within the iterator's range.
+ *
+ * @return the number of remaining values in current run within the iterator's range
+ */
+ public int getRunLength() {
+ return Math.min(runEnd, rangeEnd) - logicalPos;
+ }
+
+ /**
+ * Checks if iteration has completed.
+ *
+ * @return true if all values in the range have been processed, false otherwise
+ */
+ public boolean isEnd() {
+ return logicalPos >= rangeEnd;
+ }
+ }
}
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/StructVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/StructVector.java
index ca5f572034..5e5bb7fc21 100644
--- a/vector/src/main/java/org/apache/arrow/vector/complex/StructVector.java
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/StructVector.java
@@ -18,6 +18,7 @@
import static org.apache.arrow.memory.util.LargeMemoryUtil.checkedCastToInt;
import static org.apache.arrow.util.Preconditions.checkNotNull;
+import static org.apache.arrow.vector.BitVectorHelper.getValidityBufferSizeFromCount;
import java.util.ArrayList;
import java.util.Arrays;
@@ -89,7 +90,7 @@ public StructVector(
super(name, checkNotNull(allocator), fieldType, callBack);
this.validityBuffer = allocator.getEmpty();
this.validityAllocationSizeInBytes =
- BitVectorHelper.getValidityBufferSize(BaseValueVector.INITIAL_VALUE_ALLOCATION);
+ getValidityBufferSizeFromCount(BaseValueVector.INITIAL_VALUE_ALLOCATION);
}
/**
@@ -118,7 +119,7 @@ public StructVector(
allowConflictPolicyChanges);
this.validityBuffer = allocator.getEmpty();
this.validityAllocationSizeInBytes =
- BitVectorHelper.getValidityBufferSize(BaseValueVector.INITIAL_VALUE_ALLOCATION);
+ getValidityBufferSizeFromCount(BaseValueVector.INITIAL_VALUE_ALLOCATION);
}
/**
@@ -132,7 +133,7 @@ public StructVector(Field field, BufferAllocator allocator, CallBack callBack) {
super(field, checkNotNull(allocator), callBack);
this.validityBuffer = allocator.getEmpty();
this.validityAllocationSizeInBytes =
- BitVectorHelper.getValidityBufferSize(BaseValueVector.INITIAL_VALUE_ALLOCATION);
+ getValidityBufferSizeFromCount(BaseValueVector.INITIAL_VALUE_ALLOCATION);
}
/**
@@ -153,7 +154,7 @@ public StructVector(
super(field, checkNotNull(allocator), callBack, conflictPolicy, allowConflictPolicyChanges);
this.validityBuffer = allocator.getEmpty();
this.validityAllocationSizeInBytes =
- BitVectorHelper.getValidityBufferSize(BaseValueVector.INITIAL_VALUE_ALLOCATION);
+ getValidityBufferSizeFromCount(BaseValueVector.INITIAL_VALUE_ALLOCATION);
}
@Override
@@ -182,7 +183,7 @@ public List getFieldBuffers() {
private void setReaderAndWriterIndex() {
validityBuffer.readerIndex(0);
- validityBuffer.writerIndex(BitVectorHelper.getValidityBufferSize(valueCount));
+ validityBuffer.writerIndex(getValidityBufferSizeFromCount(valueCount));
}
/**
@@ -318,7 +319,7 @@ public void splitAndTransfer(int startIndex, int length) {
private void splitAndTransferValidityBuffer(int startIndex, int length, StructVector target) {
int firstByteSource = BitVectorHelper.byteIndex(startIndex);
int lastByteSource = BitVectorHelper.byteIndex(valueCount - 1);
- int byteSizeTarget = BitVectorHelper.getValidityBufferSize(length);
+ int byteSizeTarget = getValidityBufferSizeFromCount(length);
int offset = startIndex % 8;
if (length > 0) {
@@ -464,7 +465,7 @@ public int getBufferSize() {
if (valueCount == 0) {
return 0;
}
- return super.getBufferSize() + BitVectorHelper.getValidityBufferSize(valueCount);
+ return super.getBufferSize() + getValidityBufferSizeFromCount(valueCount);
}
/**
@@ -478,18 +479,18 @@ public int getBufferSizeFor(final int valueCount) {
if (valueCount == 0) {
return 0;
}
- return super.getBufferSizeFor(valueCount) + BitVectorHelper.getValidityBufferSize(valueCount);
+ return super.getBufferSizeFor(valueCount) + getValidityBufferSizeFromCount(valueCount);
}
@Override
public void setInitialCapacity(int numRecords) {
- validityAllocationSizeInBytes = BitVectorHelper.getValidityBufferSize(numRecords);
+ validityAllocationSizeInBytes = getValidityBufferSizeFromCount(numRecords);
super.setInitialCapacity(numRecords);
}
@Override
public void setInitialCapacity(int numRecords, double density) {
- validityAllocationSizeInBytes = BitVectorHelper.getValidityBufferSize(numRecords);
+ validityAllocationSizeInBytes = getValidityBufferSizeFromCount(numRecords);
super.setInitialCapacity(numRecords, density);
}
@@ -547,7 +548,7 @@ private long getNewAllocationSize(int currentBufferCapacity) {
newAllocationSize = validityAllocationSizeInBytes;
} else {
newAllocationSize =
- BitVectorHelper.getValidityBufferSize(BaseValueVector.INITIAL_VALUE_ALLOCATION) * 2L;
+ getValidityBufferSizeFromCount(BaseValueVector.INITIAL_VALUE_ALLOCATION) * 2L;
}
}
newAllocationSize = CommonUtil.nextPowerOfTwo(newAllocationSize);
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/impl/ExtensionTypeWriterFactory.java b/vector/src/main/java/org/apache/arrow/vector/complex/impl/ExtensionTypeWriterFactory.java
deleted file mode 100644
index 09f0314c5f..0000000000
--- a/vector/src/main/java/org/apache/arrow/vector/complex/impl/ExtensionTypeWriterFactory.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.vector.complex.impl;
-
-import org.apache.arrow.vector.ExtensionTypeVector;
-import org.apache.arrow.vector.complex.writer.FieldWriter;
-
-/**
- * A factory interface for creating instances of {@link ExtensionTypeWriter}. This factory allows
- * configuring writer implementations for specific {@link ExtensionTypeVector}.
- *
- * @param the type of writer implementation for a specific {@link ExtensionTypeVector}.
- */
-public interface ExtensionTypeWriterFactory {
-
- /**
- * Returns an instance of the writer implementation for the given {@link ExtensionTypeVector}.
- *
- * @param vector the {@link ExtensionTypeVector} for which the writer implementation is to be
- * returned.
- * @return an instance of the writer implementation for the given {@link ExtensionTypeVector}.
- */
- T getWriterImpl(ExtensionTypeVector vector);
-}
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/impl/NullableUuidHolderReaderImpl.java b/vector/src/main/java/org/apache/arrow/vector/complex/impl/NullableUuidHolderReaderImpl.java
new file mode 100644
index 0000000000..7a5312f6ed
--- /dev/null
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/impl/NullableUuidHolderReaderImpl.java
@@ -0,0 +1,123 @@
+/*
+ * 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.vector.complex.impl;
+
+import org.apache.arrow.vector.holders.ExtensionHolder;
+import org.apache.arrow.vector.holders.NullableUuidHolder;
+import org.apache.arrow.vector.holders.UuidHolder;
+import org.apache.arrow.vector.types.Types;
+import org.apache.arrow.vector.util.UuidUtility;
+
+/**
+ * Reader implementation for reading UUID values from a {@link NullableUuidHolder}.
+ *
+ *
This reader wraps a single UUID holder value and provides methods to read from it. Unlike
+ * {@link UuidReaderImpl} which reads from a vector, this reader operates on a holder instance.
+ *
+ * @see NullableUuidHolder
+ * @see UuidReaderImpl
+ */
+public class NullableUuidHolderReaderImpl extends AbstractFieldReader {
+ private final NullableUuidHolder holder;
+
+ /**
+ * Constructs a reader for the given UUID holder.
+ *
+ * @param holder the UUID holder to read from
+ */
+ public NullableUuidHolderReaderImpl(NullableUuidHolder holder) {
+ this.holder = holder;
+ }
+
+ @Override
+ public int size() {
+ throw new UnsupportedOperationException(
+ "size() is not supported on NullableUuidHolderReaderImpl. "
+ + "This reader wraps a single UUID holder value, not a collection. "
+ + "Use UuidReaderImpl for vector-based UUID reading.");
+ }
+
+ @Override
+ public boolean next() {
+ throw new UnsupportedOperationException(
+ "next() is not supported on NullableUuidHolderReaderImpl. "
+ + "This reader wraps a single UUID holder value, not an iterator. "
+ + "Use UuidReaderImpl for vector-based UUID reading.");
+ }
+
+ @Override
+ public void setPosition(int index) {
+ throw new UnsupportedOperationException(
+ "setPosition() is not supported on NullableUuidHolderReaderImpl. "
+ + "This reader wraps a single UUID holder value, not a vector. "
+ + "Use UuidReaderImpl for vector-based UUID reading.");
+ }
+
+ @Override
+ public Types.MinorType getMinorType() {
+ return Types.MinorType.EXTENSIONTYPE;
+ }
+
+ @Override
+ public boolean isSet() {
+ return holder.isSet == 1;
+ }
+
+ @Override
+ public void read(ExtensionHolder h) {
+ if (h instanceof NullableUuidHolder) {
+ NullableUuidHolder nullableHolder = (NullableUuidHolder) h;
+ nullableHolder.buffer = this.holder.buffer;
+ nullableHolder.isSet = this.holder.isSet;
+ nullableHolder.start = this.holder.start;
+ } else if (h instanceof UuidHolder) {
+ UuidHolder uuidHolder = (UuidHolder) h;
+ uuidHolder.buffer = this.holder.buffer;
+ uuidHolder.start = this.holder.start;
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported holder type: "
+ + h.getClass().getName()
+ + ". "
+ + "Only NullableUuidHolder and UuidHolder are supported for UUID values. "
+ + "Provided holder type cannot be used to read UUID data.");
+ }
+ }
+
+ @Override
+ public Object readObject() {
+ if (!isSet()) {
+ return null;
+ }
+ // Convert UUID bytes to Java UUID object
+ try {
+ return UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ } catch (Exception e) {
+ throw new RuntimeException(
+ String.format(
+ "Failed to read UUID from buffer. Invalid Arrow buffer state: "
+ + "capacity=%d, readableBytes=%d, readerIndex=%d, writerIndex=%d, refCnt=%d. "
+ + "The buffer must contain exactly 16 bytes of valid UUID data.",
+ holder.buffer.capacity(),
+ holder.buffer.readableBytes(),
+ holder.buffer.readerIndex(),
+ holder.buffer.writerIndex(),
+ holder.buffer.refCnt()),
+ e);
+ }
+ }
+}
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/impl/UnionExtensionWriter.java b/vector/src/main/java/org/apache/arrow/vector/complex/impl/UnionExtensionWriter.java
index d341384bd9..93796aa77e 100644
--- a/vector/src/main/java/org/apache/arrow/vector/complex/impl/UnionExtensionWriter.java
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/impl/UnionExtensionWriter.java
@@ -60,11 +60,6 @@ public void writeExtension(Object var1) {
}
@Override
- public void addExtensionTypeWriterFactory(ExtensionTypeWriterFactory factory) {
- this.writer = factory.getWriterImpl(vector);
- this.writer.setPosition(idx());
- }
-
public void write(ExtensionHolder holder) {
this.writer.write(holder);
}
@@ -76,4 +71,10 @@ public void setPosition(int index) {
this.writer.setPosition(index);
}
}
+
+ @Override
+ public void writeNull() {
+ this.vector.setNull(getPosition());
+ this.vector.setValueCount(getPosition() + 1);
+ }
}
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/impl/UuidReaderImpl.java b/vector/src/main/java/org/apache/arrow/vector/complex/impl/UuidReaderImpl.java
new file mode 100644
index 0000000000..bb7ae13e5b
--- /dev/null
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/impl/UuidReaderImpl.java
@@ -0,0 +1,94 @@
+/*
+ * 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.vector.complex.impl;
+
+import org.apache.arrow.vector.UuidVector;
+import org.apache.arrow.vector.holders.ExtensionHolder;
+import org.apache.arrow.vector.holders.NullableUuidHolder;
+import org.apache.arrow.vector.holders.UuidHolder;
+import org.apache.arrow.vector.types.Types.MinorType;
+import org.apache.arrow.vector.types.pojo.Field;
+
+/**
+ * Reader implementation for {@link UuidVector}.
+ *
+ *
Provides methods to read UUID values from a vector, including support for reading into {@link
+ * UuidHolder} and retrieving values as {@link java.util.UUID} objects.
+ *
+ * @see UuidVector
+ * @see org.apache.arrow.vector.extension.UuidType
+ */
+public class UuidReaderImpl extends AbstractFieldReader {
+
+ private final UuidVector vector;
+
+ /**
+ * Constructs a reader for the given UUID vector.
+ *
+ * @param vector the UUID vector to read from
+ */
+ public UuidReaderImpl(UuidVector vector) {
+ super();
+ this.vector = vector;
+ }
+
+ @Override
+ public MinorType getMinorType() {
+ return vector.getMinorType();
+ }
+
+ @Override
+ public Field getField() {
+ return vector.getField();
+ }
+
+ @Override
+ public boolean isSet() {
+ return !vector.isNull(idx());
+ }
+
+ @Override
+ public void read(ExtensionHolder holder) {
+ if (holder instanceof NullableUuidHolder) {
+ vector.get(idx(), (NullableUuidHolder) holder);
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported holder type for UuidReader: " + holder.getClass());
+ }
+ }
+
+ @Override
+ public void read(int arrayIndex, ExtensionHolder holder) {
+ if (holder instanceof NullableUuidHolder) {
+ vector.get(arrayIndex, (NullableUuidHolder) holder);
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported holder type for UuidReader: " + holder.getClass());
+ }
+ }
+
+ @Override
+ public void copyAsValue(AbstractExtensionTypeWriter writer) {
+ UuidWriterImpl impl = (UuidWriterImpl) writer;
+ impl.vector.copyFromSafe(idx(), impl.idx(), vector);
+ }
+
+ @Override
+ public Object readObject() {
+ return vector.getObject(idx());
+ }
+}
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/impl/UuidWriterImpl.java b/vector/src/main/java/org/apache/arrow/vector/complex/impl/UuidWriterImpl.java
new file mode 100644
index 0000000000..944b7e2e62
--- /dev/null
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/impl/UuidWriterImpl.java
@@ -0,0 +1,81 @@
+/*
+ * 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.vector.complex.impl;
+
+import org.apache.arrow.memory.ArrowBuf;
+import org.apache.arrow.vector.UuidVector;
+import org.apache.arrow.vector.holders.ExtensionHolder;
+import org.apache.arrow.vector.holders.NullableUuidHolder;
+import org.apache.arrow.vector.holders.UuidHolder;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+
+/**
+ * Writer implementation for {@link UuidVector}.
+ *
+ *
Supports writing UUID values in multiple formats: {@link java.util.UUID}, byte arrays, and
+ * {@link ArrowBuf}. Also handles {@link UuidHolder} and {@link NullableUuidHolder}.
+ *
+ * @see UuidVector
+ * @see org.apache.arrow.vector.extension.UuidType
+ */
+public class UuidWriterImpl extends AbstractExtensionTypeWriter {
+
+ /**
+ * Constructs a writer for the given UUID vector.
+ *
+ * @param vector the UUID vector to write to
+ */
+ public UuidWriterImpl(UuidVector vector) {
+ super(vector);
+ }
+
+ @Override
+ public void writeExtension(Object value) {
+ if (value instanceof byte[]) {
+ vector.setSafe(getPosition(), (byte[]) value);
+ } else if (value instanceof ArrowBuf) {
+ vector.setSafe(getPosition(), (ArrowBuf) value);
+ } else if (value instanceof java.util.UUID) {
+ vector.setSafe(getPosition(), (java.util.UUID) value);
+ } else if (value instanceof ExtensionHolder) {
+ write((ExtensionHolder) value);
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported value type for UUID: "
+ + value.getClass().getName()
+ + ". "
+ + "Supported types are: byte[] (16 bytes), ArrowBuf (16 bytes), or java.util.UUID. "
+ + "Convert your value to one of these types before writing.");
+ }
+ vector.setValueCount(getPosition() + 1);
+ }
+
+ @Override
+ public void writeExtension(Object value, ArrowType type) {
+ writeExtension(value);
+ }
+
+ @Override
+ public void write(ExtensionHolder holder) {
+ if (holder instanceof UuidHolder) {
+ vector.setSafe(getPosition(), (UuidHolder) holder);
+ } else if (holder instanceof NullableUuidHolder) {
+ vector.setSafe(getPosition(), (NullableUuidHolder) holder);
+ }
+ vector.setValueCount(getPosition() + 1);
+ }
+}
diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/reader/ExtensionReader.java b/vector/src/main/java/org/apache/arrow/vector/complex/reader/ExtensionReader.java
new file mode 100644
index 0000000000..1ba7b27156
--- /dev/null
+++ b/vector/src/main/java/org/apache/arrow/vector/complex/reader/ExtensionReader.java
@@ -0,0 +1,44 @@
+/*
+ * 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.vector.complex.reader;
+
+import org.apache.arrow.vector.holders.ExtensionHolder;
+
+/** Interface for reading extension types. Extends the functionality of {@link BaseReader}. */
+public interface ExtensionReader extends BaseReader {
+
+ /**
+ * Reads to the given extension holder.
+ *
+ * @param holder the {@link ExtensionHolder} to read
+ */
+ void read(ExtensionHolder holder);
+
+ /**
+ * Reads and returns an object representation of the extension type.
+ *
+ * @return the object representation of the extension type
+ */
+ Object readObject();
+
+ /**
+ * Checks if the current value is set.
+ *
+ * @return true if the value is set, false otherwise
+ */
+ boolean isSet();
+}
diff --git a/vector/src/main/java/org/apache/arrow/vector/compression/AbstractCompressionCodec.java b/vector/src/main/java/org/apache/arrow/vector/compression/AbstractCompressionCodec.java
index 58d9e4db9b..b108173c82 100644
--- a/vector/src/main/java/org/apache/arrow/vector/compression/AbstractCompressionCodec.java
+++ b/vector/src/main/java/org/apache/arrow/vector/compression/AbstractCompressionCodec.java
@@ -29,7 +29,11 @@ public abstract class AbstractCompressionCodec implements CompressionCodec {
@Override
public ArrowBuf compress(BufferAllocator allocator, ArrowBuf uncompressedBuffer) {
- if (uncompressedBuffer.writerIndex() == 0L) {
+ // GH-1116: capture writerIndex() once so the empty-buffer check, size
+ // comparison, and uncompressed-length prefix all see the same value.
+ long uncompressedLength = uncompressedBuffer.writerIndex();
+
+ if (uncompressedLength == 0L) {
// shortcut for empty buffer
ArrowBuf compressedBuffer = allocator.buffer(CompressionUtil.SIZE_OF_UNCOMPRESSED_LENGTH);
compressedBuffer.setLong(0, 0);
@@ -41,7 +45,6 @@ public ArrowBuf compress(BufferAllocator allocator, ArrowBuf uncompressedBuffer)
ArrowBuf compressedBuffer = doCompress(allocator, uncompressedBuffer);
long compressedLength =
compressedBuffer.writerIndex() - CompressionUtil.SIZE_OF_UNCOMPRESSED_LENGTH;
- long uncompressedLength = uncompressedBuffer.writerIndex();
if (compressedLength > uncompressedLength) {
// compressed buffer is larger, send the raw buffer
diff --git a/vector/src/main/java/org/apache/arrow/vector/extension/OpaqueType.java b/vector/src/main/java/org/apache/arrow/vector/extension/OpaqueType.java
index ca56214fda..780a4ee659 100644
--- a/vector/src/main/java/org/apache/arrow/vector/extension/OpaqueType.java
+++ b/vector/src/main/java/org/apache/arrow/vector/extension/OpaqueType.java
@@ -54,10 +54,12 @@
import org.apache.arrow.vector.TimeStampNanoVector;
import org.apache.arrow.vector.TimeStampSecTZVector;
import org.apache.arrow.vector.TimeStampSecVector;
+import org.apache.arrow.vector.ValueVector;
import org.apache.arrow.vector.VarBinaryVector;
import org.apache.arrow.vector.VarCharVector;
import org.apache.arrow.vector.ViewVarBinaryVector;
import org.apache.arrow.vector.ViewVarCharVector;
+import org.apache.arrow.vector.complex.writer.FieldWriter;
import org.apache.arrow.vector.types.Types;
import org.apache.arrow.vector.types.pojo.ArrowType;
import org.apache.arrow.vector.types.pojo.ExtensionTypeRegistry;
@@ -177,6 +179,11 @@ public int hashCode() {
return Objects.hash(super.hashCode(), storageType, typeName, vendorName);
}
+ @Override
+ public FieldWriter getNewFieldWriter(ValueVector vector) {
+ throw new UnsupportedOperationException("WriterImpl not yet implemented.");
+ }
+
@Override
public String toString() {
return "OpaqueType("
diff --git a/vector/src/main/java/org/apache/arrow/vector/extension/UuidType.java b/vector/src/main/java/org/apache/arrow/vector/extension/UuidType.java
new file mode 100644
index 0000000000..c249c6eda9
--- /dev/null
+++ b/vector/src/main/java/org/apache/arrow/vector/extension/UuidType.java
@@ -0,0 +1,119 @@
+/*
+ * 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.vector.extension;
+
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.vector.FieldVector;
+import org.apache.arrow.vector.FixedSizeBinaryVector;
+import org.apache.arrow.vector.UuidVector;
+import org.apache.arrow.vector.ValueVector;
+import org.apache.arrow.vector.complex.impl.UuidWriterImpl;
+import org.apache.arrow.vector.complex.writer.FieldWriter;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.ArrowType.ExtensionType;
+import org.apache.arrow.vector.types.pojo.ExtensionTypeRegistry;
+import org.apache.arrow.vector.types.pojo.FieldType;
+
+/**
+ * Extension type for UUID (Universally Unique Identifier) values.
+ *
+ *
UUIDs are stored as 16-byte fixed-size binary values. This extension type provides a
+ * standardized way to represent UUIDs in Arrow, making them interoperable across different systems
+ * and languages.Ï€
+ *
+ *
The extension name is "arrow.uuid" and it uses {@link ArrowType.FixedSizeBinary} with 16 bytes
+ * as the storage type.
+ *
+ *
+ *
+ * @see UuidVector
+ * @see org.apache.arrow.vector.holders.UuidHolder
+ * @see org.apache.arrow.vector.holders.NullableUuidHolder
+ */
+public class UuidType extends ExtensionType {
+ /** Singleton instance of UuidType. */
+ public static final UuidType INSTANCE = new UuidType();
+
+ /** Extension name registered in the Arrow extension type registry. */
+ public static final String EXTENSION_NAME = "arrow.uuid";
+
+ /** Number of bytes used to store a UUID (128 bits = 16 bytes). */
+ public static final int UUID_BYTE_WIDTH = 16;
+
+ /** Number of characters in the standard UUID string representation (with hyphens). */
+ public static final int UUID_STRING_WIDTH = 36;
+
+ /** Storage type for UUID: FixedSizeBinary(16). */
+ public static final ArrowType STORAGE_TYPE = new ArrowType.FixedSizeBinary(UUID_BYTE_WIDTH);
+
+ private UuidType() {}
+
+ static {
+ ExtensionTypeRegistry.register(INSTANCE);
+ }
+
+ @Override
+ public ArrowType storageType() {
+ return STORAGE_TYPE;
+ }
+
+ @Override
+ public String extensionName() {
+ return EXTENSION_NAME;
+ }
+
+ @Override
+ public boolean extensionEquals(ExtensionType other) {
+ return other instanceof UuidType;
+ }
+
+ @Override
+ public ArrowType deserialize(ArrowType storageType, String serializedData) {
+ if (!storageType.equals(storageType())) {
+ throw new UnsupportedOperationException(
+ "Cannot construct UuidType from underlying type " + storageType);
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public String serialize() {
+ return "";
+ }
+
+ @Override
+ public boolean isComplex() {
+ return false;
+ }
+
+ @Override
+ public FieldVector getNewVector(String name, FieldType fieldType, BufferAllocator allocator) {
+ return new UuidVector(
+ name, fieldType, allocator, new FixedSizeBinaryVector(name, allocator, UUID_BYTE_WIDTH));
+ }
+
+ @Override
+ public FieldWriter getNewFieldWriter(ValueVector vector) {
+ return new UuidWriterImpl((UuidVector) vector);
+ }
+}
diff --git a/vector/src/main/java/org/apache/arrow/vector/holders/ExtensionHolder.java b/vector/src/main/java/org/apache/arrow/vector/holders/ExtensionHolder.java
index fc7ed85878..4d3f767aef 100644
--- a/vector/src/main/java/org/apache/arrow/vector/holders/ExtensionHolder.java
+++ b/vector/src/main/java/org/apache/arrow/vector/holders/ExtensionHolder.java
@@ -16,7 +16,11 @@
*/
package org.apache.arrow.vector.holders;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+
/** Base {@link ValueHolder} class for a {@link org.apache.arrow.vector.ExtensionTypeVector}. */
public abstract class ExtensionHolder implements ValueHolder {
public int isSet;
+
+ public abstract ArrowType type();
}
diff --git a/vector/src/main/java/org/apache/arrow/vector/holders/NullableUuidHolder.java b/vector/src/main/java/org/apache/arrow/vector/holders/NullableUuidHolder.java
new file mode 100644
index 0000000000..6a2b4ff604
--- /dev/null
+++ b/vector/src/main/java/org/apache/arrow/vector/holders/NullableUuidHolder.java
@@ -0,0 +1,45 @@
+/*
+ * 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.vector.holders;
+
+import org.apache.arrow.memory.ArrowBuf;
+import org.apache.arrow.vector.extension.UuidType;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+
+/**
+ * Value holder for nullable UUID values.
+ *
+ *
The {@code isSet} field controls nullability: when {@code isSet = 1}, the holder contains a
+ * valid UUID in {@code buffer}; when {@code isSet = 0}, the holder represents a null value and
+ * {@code buffer} should not be accessed.
+ *
+ * @see UuidHolder
+ * @see org.apache.arrow.vector.UuidVector
+ * @see org.apache.arrow.vector.extension.UuidType
+ */
+public class NullableUuidHolder extends ExtensionHolder {
+ /** Buffer containing 16-byte UUID data. */
+ public ArrowBuf buffer;
+
+ /** Offset in the buffer where the UUID data starts. */
+ public int start = 0;
+
+ @Override
+ public ArrowType type() {
+ return UuidType.INSTANCE;
+ }
+}
diff --git a/vector/src/main/java/org/apache/arrow/vector/holders/UuidHolder.java b/vector/src/main/java/org/apache/arrow/vector/holders/UuidHolder.java
new file mode 100644
index 0000000000..9ec0305f30
--- /dev/null
+++ b/vector/src/main/java/org/apache/arrow/vector/holders/UuidHolder.java
@@ -0,0 +1,48 @@
+/*
+ * 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.vector.holders;
+
+import org.apache.arrow.memory.ArrowBuf;
+import org.apache.arrow.vector.extension.UuidType;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+
+/**
+ * Value holder for non-nullable UUID values.
+ *
+ *
Contains a 16-byte UUID in {@code buffer} with {@code isSet} always 1.
+ *
+ * @see NullableUuidHolder
+ * @see org.apache.arrow.vector.UuidVector
+ * @see org.apache.arrow.vector.extension.UuidType
+ */
+public class UuidHolder extends ExtensionHolder {
+ /** Buffer containing 16-byte UUID data. */
+ public ArrowBuf buffer;
+
+ /** Offset in the buffer where the UUID data starts. */
+ public int start = 0;
+
+ /** Constructs a UuidHolder with isSet = 1. */
+ public UuidHolder() {
+ this.isSet = 1;
+ }
+
+ @Override
+ public ArrowType type() {
+ return UuidType.INSTANCE;
+ }
+}
diff --git a/vector/src/main/java/org/apache/arrow/vector/ipc/JsonFileReader.java b/vector/src/main/java/org/apache/arrow/vector/ipc/JsonFileReader.java
index fe0803d298..e4bab7eb80 100644
--- a/vector/src/main/java/org/apache/arrow/vector/ipc/JsonFileReader.java
+++ b/vector/src/main/java/org/apache/arrow/vector/ipc/JsonFileReader.java
@@ -20,6 +20,7 @@
import static com.fasterxml.jackson.core.JsonToken.END_OBJECT;
import static com.fasterxml.jackson.core.JsonToken.START_ARRAY;
import static com.fasterxml.jackson.core.JsonToken.START_OBJECT;
+import static org.apache.arrow.vector.BitVectorHelper.getValidityBufferSizeFromCount;
import static org.apache.arrow.vector.BufferLayout.BufferType.DATA;
import static org.apache.arrow.vector.BufferLayout.BufferType.OFFSET;
import static org.apache.arrow.vector.BufferLayout.BufferType.SIZE;
@@ -381,7 +382,7 @@ private class BufferHelper {
new BufferReader() {
@Override
protected ArrowBuf read(BufferAllocator allocator, int count) throws IOException {
- final int bufferSize = BitVectorHelper.getValidityBufferSize(count);
+ final int bufferSize = getValidityBufferSizeFromCount(count);
ArrowBuf buf = allocator.buffer(bufferSize);
// C++ integration test fails without this.
diff --git a/vector/src/main/java/org/apache/arrow/vector/util/UuidUtility.java b/vector/src/main/java/org/apache/arrow/vector/util/UuidUtility.java
new file mode 100644
index 0000000000..a1b0b54579
--- /dev/null
+++ b/vector/src/main/java/org/apache/arrow/vector/util/UuidUtility.java
@@ -0,0 +1,77 @@
+/*
+ * 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.vector.util;
+
+import static org.apache.arrow.vector.extension.UuidType.UUID_BYTE_WIDTH;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.UUID;
+import org.apache.arrow.memory.ArrowBuf;
+
+/**
+ * Utility class for UUID conversions and operations.
+ *
+ *
Provides methods to convert between {@link UUID} objects and byte representations used in
+ * Arrow vectors.
+ *
+ * @see org.apache.arrow.vector.UuidVector
+ * @see org.apache.arrow.vector.extension.UuidType
+ */
+public class UuidUtility {
+ /**
+ * Converts a UUID to a 16-byte array.
+ *
+ *
The UUID is stored in big-endian byte order, with the most significant bits first.
+ *
+ * @param uuid the UUID to convert
+ * @return a 16-byte array representing the UUID
+ */
+ public static byte[] getBytesFromUUID(UUID uuid) {
+ byte[] result = new byte[16];
+ long msb = uuid.getMostSignificantBits();
+ long lsb = uuid.getLeastSignificantBits();
+ for (int i = 15; i >= 8; i--) {
+ result[i] = (byte) (lsb & 0xFF);
+ lsb >>= 8;
+ }
+ for (int i = 7; i >= 0; i--) {
+ result[i] = (byte) (msb & 0xFF);
+ msb >>= 8;
+ }
+ return result;
+ }
+
+ /**
+ * Constructs a UUID from bytes stored in an ArrowBuf at the specified index.
+ *
+ *
Reads 16 bytes from the buffer starting at the given index and interprets them as a UUID in
+ * big-endian byte order.
+ *
+ * @param buffer the buffer containing UUID data
+ * @param index the byte offset in the buffer where the UUID starts
+ * @return the UUID constructed from the buffer data
+ */
+ public static UUID uuidFromArrowBuf(ArrowBuf buffer, long index) {
+ ByteBuffer buf = buffer.nioBuffer(index, UUID_BYTE_WIDTH);
+
+ buf.order(ByteOrder.BIG_ENDIAN);
+ long mostSigBits = buf.getLong(0);
+ long leastSigBits = buf.getLong(Long.BYTES);
+ return new UUID(mostSigBits, leastSigBits);
+ }
+}
diff --git a/vector/src/main/java/org/apache/arrow/vector/util/VectorAppender.java b/vector/src/main/java/org/apache/arrow/vector/util/VectorAppender.java
index 0dc96a4d4b..2cfeb0a04d 100644
--- a/vector/src/main/java/org/apache/arrow/vector/util/VectorAppender.java
+++ b/vector/src/main/java/org/apache/arrow/vector/util/VectorAppender.java
@@ -24,13 +24,17 @@
import org.apache.arrow.memory.util.MemoryUtil;
import org.apache.arrow.util.Preconditions;
import org.apache.arrow.vector.BaseFixedWidthVector;
+import org.apache.arrow.vector.BaseIntVector;
import org.apache.arrow.vector.BaseLargeVariableWidthVector;
import org.apache.arrow.vector.BaseVariableWidthVector;
import org.apache.arrow.vector.BaseVariableWidthViewVector;
+import org.apache.arrow.vector.BigIntVector;
import org.apache.arrow.vector.BitVector;
import org.apache.arrow.vector.BitVectorHelper;
import org.apache.arrow.vector.ExtensionTypeVector;
+import org.apache.arrow.vector.IntVector;
import org.apache.arrow.vector.NullVector;
+import org.apache.arrow.vector.SmallIntVector;
import org.apache.arrow.vector.ValueVector;
import org.apache.arrow.vector.compare.TypeEqualsVisitor;
import org.apache.arrow.vector.compare.VectorVisitor;
@@ -39,6 +43,7 @@
import org.apache.arrow.vector.complex.LargeListVector;
import org.apache.arrow.vector.complex.ListVector;
import org.apache.arrow.vector.complex.NonNullableStructVector;
+import org.apache.arrow.vector.complex.RunEndEncodedVector;
import org.apache.arrow.vector.complex.UnionVector;
/** Utility to append two vectors together. */
@@ -120,10 +125,15 @@ public ValueVector visit(BaseVariableWidthVector deltaVector, Void value) {
targetVector
.getOffsetBuffer()
.getInt((long) targetVector.getValueCount() * BaseVariableWidthVector.OFFSET_WIDTH);
+ // The delta vector's offset buffer need not start at zero (e.g. a vector imported through
+ // the C data interface from a sliced array), so the amount of data to append is the
+ // distance between its first and last offsets, not the last offset itself.
+ int deltaDataStart = deltaVector.getOffsetBuffer().getInt(0);
int deltaDataSize =
deltaVector
- .getOffsetBuffer()
- .getInt((long) deltaVector.getValueCount() * BaseVariableWidthVector.OFFSET_WIDTH);
+ .getOffsetBuffer()
+ .getInt((long) deltaVector.getValueCount() * BaseVariableWidthVector.OFFSET_WIDTH)
+ - deltaDataStart;
int newValueCapacity = targetDataSize + deltaDataSize;
// make sure there is enough capacity
@@ -144,7 +154,7 @@ public ValueVector visit(BaseVariableWidthVector deltaVector, Void value) {
// append data buffer
MemoryUtil.copyMemory(
- deltaVector.getDataBuffer().memoryAddress(),
+ deltaVector.getDataBuffer().memoryAddress() + deltaDataStart,
targetVector.getDataBuffer().memoryAddress() + targetDataSize,
deltaDataSize);
@@ -155,7 +165,7 @@ public ValueVector visit(BaseVariableWidthVector deltaVector, Void value) {
+ (targetVector.getValueCount() + 1) * BaseVariableWidthVector.OFFSET_WIDTH,
deltaVector.getValueCount() * BaseVariableWidthVector.OFFSET_WIDTH);
- // increase each offset from the second buffer
+ // rebase each appended offset to the target's data, accounting for the delta's start offset
for (int i = 0; i < deltaVector.getValueCount(); i++) {
int oldOffset =
targetVector
@@ -167,7 +177,7 @@ public ValueVector visit(BaseVariableWidthVector deltaVector, Void value) {
.getOffsetBuffer()
.setInt(
(long) (targetVector.getValueCount() + 1 + i) * BaseVariableWidthVector.OFFSET_WIDTH,
- oldOffset + targetDataSize);
+ oldOffset - deltaDataStart + targetDataSize);
}
((BaseVariableWidthVector) targetVector).setLastSet(newValueCount - 1);
targetVector.setValueCount(newValueCount);
@@ -191,11 +201,15 @@ public ValueVector visit(BaseLargeVariableWidthVector deltaVector, Void value) {
.getOffsetBuffer()
.getLong(
(long) targetVector.getValueCount() * BaseLargeVariableWidthVector.OFFSET_WIDTH);
+ // see the corresponding comment in visit(BaseVariableWidthVector, Void): the delta's
+ // offset buffer need not start at zero
+ long deltaDataStart = deltaVector.getOffsetBuffer().getLong(0);
long deltaDataSize =
deltaVector
- .getOffsetBuffer()
- .getLong(
- (long) deltaVector.getValueCount() * BaseLargeVariableWidthVector.OFFSET_WIDTH);
+ .getOffsetBuffer()
+ .getLong(
+ (long) deltaVector.getValueCount() * BaseLargeVariableWidthVector.OFFSET_WIDTH)
+ - deltaDataStart;
long newValueCapacity = targetDataSize + deltaDataSize;
// make sure there is enough capacity
@@ -216,7 +230,7 @@ public ValueVector visit(BaseLargeVariableWidthVector deltaVector, Void value) {
// append data buffer
MemoryUtil.copyMemory(
- deltaVector.getDataBuffer().memoryAddress(),
+ deltaVector.getDataBuffer().memoryAddress() + deltaDataStart,
targetVector.getDataBuffer().memoryAddress() + targetDataSize,
deltaDataSize);
@@ -227,7 +241,7 @@ public ValueVector visit(BaseLargeVariableWidthVector deltaVector, Void value) {
+ (targetVector.getValueCount() + 1) * BaseLargeVariableWidthVector.OFFSET_WIDTH,
deltaVector.getValueCount() * BaseLargeVariableWidthVector.OFFSET_WIDTH);
- // increase each offset from the second buffer
+ // rebase each appended offset to the target's data, accounting for the delta's start offset
for (int i = 0; i < deltaVector.getValueCount(); i++) {
long oldOffset =
targetVector
@@ -240,7 +254,7 @@ public ValueVector visit(BaseLargeVariableWidthVector deltaVector, Void value) {
.setLong(
(long) (targetVector.getValueCount() + 1 + i)
* BaseLargeVariableWidthVector.OFFSET_WIDTH,
- oldOffset + targetDataSize);
+ oldOffset - deltaDataStart + targetDataSize);
}
((BaseLargeVariableWidthVector) targetVector).setLastSet(newValueCount - 1);
targetVector.setValueCount(newValueCount);
@@ -326,16 +340,20 @@ public ValueVector visit(ListVector deltaVector, Void value) {
targetVector
.getOffsetBuffer()
.getInt((long) targetVector.getValueCount() * ListVector.OFFSET_WIDTH);
- int deltaListSize =
+ // see the corresponding comment in visit(BaseVariableWidthVector, Void): the delta's
+ // offset buffer need not start at zero
+ int deltaListStart = deltaVector.getOffsetBuffer().getInt(0);
+ int deltaListEnd =
deltaVector
.getOffsetBuffer()
.getInt((long) deltaVector.getValueCount() * ListVector.OFFSET_WIDTH);
+ int deltaListSize = deltaListEnd - deltaListStart;
ListVector targetListVector = (ListVector) targetVector;
// make sure the underlying vector has value count set
targetListVector.getDataVector().setValueCount(targetListSize);
- deltaVector.getDataVector().setValueCount(deltaListSize);
+ deltaVector.getDataVector().setValueCount(deltaListEnd);
// make sure there is enough capacity
while (targetVector.getValueCapacity() < newValueCount) {
@@ -367,13 +385,16 @@ public ValueVector visit(ListVector deltaVector, Void value) {
.getOffsetBuffer()
.setInt(
(long) (targetVector.getValueCount() + 1 + i) * ListVector.OFFSET_WIDTH,
- oldOffset + targetListSize);
+ oldOffset - deltaListStart + targetListSize);
}
targetListVector.setLastSet(newValueCount - 1);
// append underlying vectors
- VectorAppender innerAppender = new VectorAppender(targetListVector.getDataVector());
- deltaVector.getDataVector().accept(innerAppender, null);
+ appendDataVector(
+ targetListVector.getDataVector(),
+ deltaVector.getDataVector(),
+ deltaListStart,
+ deltaListSize);
targetVector.setValueCount(newValueCount);
return targetVector;
@@ -395,17 +416,21 @@ public ValueVector visit(LargeListVector deltaVector, Void value) {
targetVector
.getOffsetBuffer()
.getLong((long) targetVector.getValueCount() * LargeListVector.OFFSET_WIDTH);
- long deltaListSize =
+ // see the corresponding comment in visit(BaseVariableWidthVector, Void): the delta's
+ // offset buffer need not start at zero
+ long deltaListStart = deltaVector.getOffsetBuffer().getLong(0);
+ long deltaListEnd =
deltaVector
.getOffsetBuffer()
.getLong((long) deltaVector.getValueCount() * LargeListVector.OFFSET_WIDTH);
+ long deltaListSize = deltaListEnd - deltaListStart;
- ListVector targetListVector = (ListVector) targetVector;
+ LargeListVector targetListVector = (LargeListVector) targetVector;
// make sure the underlying vector has value count set
// todo recheck these casts when int64 vectors are supported
targetListVector.getDataVector().setValueCount(checkedCastToInt(targetListSize));
- deltaVector.getDataVector().setValueCount(checkedCastToInt(deltaListSize));
+ deltaVector.getDataVector().setValueCount(checkedCastToInt(deltaListEnd));
// make sure there is enough capacity
while (targetVector.getValueCapacity() < newValueCount) {
@@ -422,10 +447,10 @@ public ValueVector visit(LargeListVector deltaVector, Void value) {
// append offset buffer
MemoryUtil.copyMemory(
- deltaVector.getOffsetBuffer().memoryAddress() + ListVector.OFFSET_WIDTH,
+ deltaVector.getOffsetBuffer().memoryAddress() + LargeListVector.OFFSET_WIDTH,
targetVector.getOffsetBuffer().memoryAddress()
+ (targetVector.getValueCount() + 1) * LargeListVector.OFFSET_WIDTH,
- (long) deltaVector.getValueCount() * ListVector.OFFSET_WIDTH);
+ (long) deltaVector.getValueCount() * LargeListVector.OFFSET_WIDTH);
// increase each offset from the second buffer
for (int i = 0; i < deltaVector.getValueCount(); i++) {
@@ -438,18 +463,42 @@ public ValueVector visit(LargeListVector deltaVector, Void value) {
.getOffsetBuffer()
.setLong(
(long) (targetVector.getValueCount() + 1 + i) * LargeListVector.OFFSET_WIDTH,
- oldOffset + targetListSize);
+ oldOffset - deltaListStart + targetListSize);
}
targetListVector.setLastSet(newValueCount - 1);
// append underlying vectors
- VectorAppender innerAppender = new VectorAppender(targetListVector.getDataVector());
- deltaVector.getDataVector().accept(innerAppender, null);
+ appendDataVector(
+ targetListVector.getDataVector(),
+ deltaVector.getDataVector(),
+ checkedCastToInt(deltaListStart),
+ checkedCastToInt(deltaListSize));
targetVector.setValueCount(newValueCount);
return targetVector;
}
+ /**
+ * Appends the range [start, start + length) of the delta vector's data vector to the target
+ * vector's data vector. The range may not cover the whole delta data vector when the delta's
+ * offset buffer does not start at zero.
+ */
+ private static void appendDataVector(
+ ValueVector targetDataVector, ValueVector deltaDataVector, int start, int length) {
+ if (start == 0 && length == deltaDataVector.getValueCount()) {
+ VectorAppender innerAppender = new VectorAppender(targetDataVector);
+ deltaDataVector.accept(innerAppender, null);
+ return;
+ }
+ TransferPair transferPair =
+ deltaDataVector.getTransferPair(deltaDataVector.getField(), deltaDataVector.getAllocator());
+ transferPair.splitAndTransfer(start, length);
+ try (ValueVector slicedDeltaDataVector = transferPair.getTo()) {
+ VectorAppender innerAppender = new VectorAppender(targetDataVector);
+ slicedDeltaDataVector.accept(innerAppender, null);
+ }
+ }
+
@Override
public ValueVector visit(FixedSizeListVector deltaVector, Void value) {
Preconditions.checkArgument(
@@ -698,4 +747,98 @@ public ValueVector visit(ExtensionTypeVector> deltaVector, Void value) {
deltaVector.getUnderlyingVector().accept(underlyingAppender, null);
return targetVector;
}
+
+ @Override
+ public ValueVector visit(RunEndEncodedVector deltaVector, Void value) {
+ Preconditions.checkArgument(
+ typeVisitor.equals(deltaVector),
+ "The deltaVector to append must have the same type as the targetVector");
+
+ if (deltaVector.getValueCount() == 0) {
+ return targetVector; // optimization, nothing to append, return
+ }
+
+ RunEndEncodedVector targetEncodedVector = (RunEndEncodedVector) targetVector;
+
+ final int targetLogicalValueCount = targetEncodedVector.getValueCount();
+
+ // Append the values vector first.
+ VectorAppender valueAppender = new VectorAppender(targetEncodedVector.getValuesVector());
+ deltaVector.getValuesVector().accept(valueAppender, null);
+
+ // Then append the run-ends vector.
+ BaseIntVector targetRunEndsVector = (BaseIntVector) targetEncodedVector.getRunEndsVector();
+ BaseIntVector deltaRunEndsVector = (BaseIntVector) deltaVector.getRunEndsVector();
+ appendRunEndsVector(targetRunEndsVector, deltaRunEndsVector, targetLogicalValueCount);
+
+ targetEncodedVector.setValueCount(targetLogicalValueCount + deltaVector.getValueCount());
+ return targetVector;
+ }
+
+ private void appendRunEndsVector(
+ BaseIntVector targetRunEndsVector,
+ BaseIntVector deltaRunEndsVector,
+ int targetLogicalValueCount) {
+ int targetPhysicalValueCount = targetRunEndsVector.getValueCount();
+ int newPhysicalValueCount = targetPhysicalValueCount + deltaRunEndsVector.getValueCount();
+
+ // make sure there is enough capacity
+ while (targetVector.getValueCapacity() < newPhysicalValueCount) {
+ targetVector.reAlloc();
+ }
+
+ // append validity buffer
+ BitVectorHelper.concatBits(
+ targetRunEndsVector.getValidityBuffer(),
+ targetRunEndsVector.getValueCount(),
+ deltaRunEndsVector.getValidityBuffer(),
+ deltaRunEndsVector.getValueCount(),
+ targetRunEndsVector.getValidityBuffer());
+
+ // shift and append data buffer
+ shiftAndAppendRunEndsDataBuffer(
+ targetRunEndsVector,
+ targetPhysicalValueCount,
+ deltaRunEndsVector.getDataBuffer(),
+ targetLogicalValueCount,
+ deltaRunEndsVector.getValueCount());
+
+ targetRunEndsVector.setValueCount(newPhysicalValueCount);
+ }
+
+ private void shiftAndAppendRunEndsDataBuffer(
+ BaseIntVector toRunEndVector,
+ int toIndex,
+ ArrowBuf fromRunEndBuffer,
+ int offset,
+ int physicalLength) {
+ ArrowBuf toRunEndBuffer = toRunEndVector.getDataBuffer();
+ if (toRunEndVector instanceof SmallIntVector) {
+ byte typeWidth = SmallIntVector.TYPE_WIDTH;
+ for (int i = 0; i < physicalLength; i++) {
+ toRunEndBuffer.setShort(
+ (long) (i + toIndex) * typeWidth,
+ fromRunEndBuffer.getShort((long) (i) * typeWidth) + offset);
+ }
+
+ } else if (toRunEndVector instanceof IntVector) {
+ byte typeWidth = IntVector.TYPE_WIDTH;
+ for (int i = 0; i < physicalLength; i++) {
+ toRunEndBuffer.setInt(
+ (long) (i + toIndex) * typeWidth,
+ fromRunEndBuffer.getInt((long) (i) * typeWidth) + offset);
+ }
+
+ } else if (toRunEndVector instanceof BigIntVector) {
+ byte typeWidth = BigIntVector.TYPE_WIDTH;
+ for (int i = 0; i < physicalLength; i++) {
+ toRunEndBuffer.setLong(
+ (long) (i + toIndex) * typeWidth,
+ fromRunEndBuffer.getLong((long) (i) * typeWidth) + offset);
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Run-end vector and must be of type int with size 16, 32, or 64 bits.");
+ }
+ }
}
diff --git a/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorBufferVisitor.java b/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorBufferVisitor.java
index 5c7215437f..5cfe64b14e 100644
--- a/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorBufferVisitor.java
+++ b/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorBufferVisitor.java
@@ -52,14 +52,22 @@ private void validateVectorCommon(ValueVector vector) {
if (vector instanceof FieldVector) {
FieldVector fieldVector = (FieldVector) vector;
- // TODO: https://github.com/apache/arrow/issues/41734
int typeBufferCount = TypeLayout.getTypeBufferCount(arrowType);
- validateOrThrow(
- fieldVector.getFieldBuffers().size() == typeBufferCount,
- "Expected %s buffers in vector of type %s, got %s.",
- typeBufferCount,
- vector.getField().getType().toString(),
- fieldVector.getFieldBuffers().size());
+ if (TypeLayout.getTypeLayout(arrowType).isFixedBufferCount()) {
+ validateOrThrow(
+ fieldVector.getFieldBuffers().size() == typeBufferCount,
+ "Expected %s buffers in vector of type %s, got %s.",
+ typeBufferCount,
+ vector.getField().getType().toString(),
+ fieldVector.getFieldBuffers().size());
+ } else {
+ validateOrThrow(
+ fieldVector.getFieldBuffers().size() >= typeBufferCount,
+ "Expected at least %s buffers in vector of type %s, got %s.",
+ typeBufferCount,
+ vector.getField().getType().toString(),
+ fieldVector.getFieldBuffers().size());
+ }
}
}
@@ -158,7 +166,12 @@ public Void visit(BaseLargeVariableWidthVector vector, Void value) {
@Override
public Void visit(BaseVariableWidthViewVector vector, Void value) {
- throw new UnsupportedOperationException("View vectors are not supported.");
+ final int valueCount = vector.getValueCount();
+ validateVectorCommon(vector);
+ validateOrThrow(vector.getFieldBuffers().size() >= 2, "Expected at least 2 buffers.");
+ validateValidityBuffer(vector, valueCount);
+ validateDataBuffer(vector, (long) valueCount * BaseVariableWidthViewVector.ELEMENT_SIZE);
+ return null;
}
@Override
diff --git a/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorDataVisitor.java b/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorDataVisitor.java
index c62bff79f7..9da8cc813e 100644
--- a/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorDataVisitor.java
+++ b/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorDataVisitor.java
@@ -121,7 +121,8 @@ public Void visit(BaseLargeVariableWidthVector vector, Void value) {
@Override
public Void visit(BaseVariableWidthViewVector vector, Void value) {
- throw new UnsupportedOperationException("View vectors are not supported.");
+ vector.validateScalars();
+ return null;
}
@Override
diff --git a/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorTypeVisitor.java b/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorTypeVisitor.java
index daad41dbdc..395852ef79 100644
--- a/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorTypeVisitor.java
+++ b/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorTypeVisitor.java
@@ -61,6 +61,8 @@
import org.apache.arrow.vector.ValueVector;
import org.apache.arrow.vector.VarBinaryVector;
import org.apache.arrow.vector.VarCharVector;
+import org.apache.arrow.vector.ViewVarBinaryVector;
+import org.apache.arrow.vector.ViewVarCharVector;
import org.apache.arrow.vector.compare.VectorVisitor;
import org.apache.arrow.vector.complex.DenseUnionVector;
import org.apache.arrow.vector.complex.FixedSizeListVector;
@@ -380,7 +382,12 @@ public Void visit(BaseLargeVariableWidthVector vector, Void value) {
@Override
public Void visit(BaseVariableWidthViewVector vector, Void value) {
- throw new UnsupportedOperationException("View vectors are not supported.");
+ if (vector instanceof ViewVarCharVector) {
+ validateVectorCommon(vector, ArrowType.Utf8View.class);
+ } else if (vector instanceof ViewVarBinaryVector) {
+ validateVectorCommon(vector, ArrowType.BinaryView.class);
+ }
+ return null;
}
@Override
diff --git a/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorVisitor.java b/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorVisitor.java
index 5004ba488c..2111410016 100644
--- a/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorVisitor.java
+++ b/vector/src/main/java/org/apache/arrow/vector/validate/ValidateVectorVisitor.java
@@ -107,8 +107,13 @@ public Void visit(BaseLargeVariableWidthVector left, Void value) {
}
@Override
- public Void visit(BaseVariableWidthViewVector left, Void value) {
- throw new UnsupportedOperationException("View vectors are not supported.");
+ public Void visit(BaseVariableWidthViewVector vector, Void value) {
+ if (vector.getValueCount() > 0) {
+ if (vector.getDataBuffer() == null || vector.getDataBuffer().capacity() == 0) {
+ throw new IllegalArgumentException("valueBuffer is null or capacity is 0");
+ }
+ }
+ return null;
}
@Override
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestFixedSizeListVector.java b/vector/src/test/java/org/apache/arrow/vector/TestFixedSizeListVector.java
index 73a88b3a1e..b3455fe52c 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestFixedSizeListVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestFixedSizeListVector.java
@@ -30,14 +30,21 @@
import java.util.Arrays;
import java.util.List;
import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.vector.complex.BaseRepeatedValueVector;
import org.apache.arrow.vector.complex.FixedSizeListVector;
import org.apache.arrow.vector.complex.ListVector;
import org.apache.arrow.vector.complex.impl.UnionFixedSizeListReader;
import org.apache.arrow.vector.complex.impl.UnionFixedSizeListWriter;
import org.apache.arrow.vector.complex.impl.UnionListReader;
import org.apache.arrow.vector.complex.reader.FieldReader;
+import org.apache.arrow.vector.holders.DurationHolder;
+import org.apache.arrow.vector.holders.FixedSizeBinaryHolder;
+import org.apache.arrow.vector.holders.TimeStampMilliTZHolder;
+import org.apache.arrow.vector.holders.TimeStampNanoTZHolder;
+import org.apache.arrow.vector.types.TimeUnit;
import org.apache.arrow.vector.types.Types.MinorType;
import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.FieldType;
import org.apache.arrow.vector.util.Text;
import org.apache.arrow.vector.util.TransferPair;
@@ -628,6 +635,206 @@ public void testWriteLargeVarBinaryHelpers() throws Exception {
}
}
+ @Test
+ public void testWriterTimeStampNanoTZField() {
+ try (final FixedSizeListVector vector =
+ FixedSizeListVector.empty("vector", /* size= */ 3, allocator)) {
+ UnionFixedSizeListWriter writer = vector.getWriter();
+ writer.allocate();
+
+ final int valueCount = 10;
+
+ for (int i = 0; i < valueCount; i++) {
+ writer.startList();
+ writer.timeStampNanoTZ().writeTimeStampNanoTZ(i * 1000L);
+ writer.timeStampNanoTZ().writeTimeStampNanoTZ((i + 1) * 1000L);
+ writer.timeStampNanoTZ().writeTimeStampNanoTZ((i + 2) * 1000L);
+ writer.endList();
+ }
+ vector.setValueCount(valueCount);
+
+ UnionFixedSizeListReader reader = vector.getReader();
+ for (int i = 0; i < valueCount; i++) {
+ reader.setPosition(i);
+ assertTrue(reader.isSet());
+ assertTrue(reader.next());
+ assertEquals(i * 1000L, reader.reader().readLong().longValue());
+ assertTrue(reader.next());
+ assertEquals((i + 1) * 1000L, reader.reader().readLong().longValue());
+ assertTrue(reader.next());
+ assertEquals((i + 2) * 1000L, reader.reader().readLong().longValue());
+ assertFalse(reader.next());
+ }
+ }
+ }
+
+ @Test
+ public void testWriterUsingHolderTimeStampNanoTZField() {
+ try (final FixedSizeListVector vector =
+ FixedSizeListVector.empty("vector", /* size= */ 3, allocator)) {
+ UnionFixedSizeListWriter writer = vector.getWriter();
+ writer.allocate();
+
+ TimeStampNanoTZHolder holder = new TimeStampNanoTZHolder();
+ holder.timezone = "SomeFakeTimeZone";
+ writer.startList();
+ holder.value = 12341234L;
+ writer.timeStampNanoTZ().write(holder);
+ holder.value = 55555L;
+ writer.timeStampNanoTZ().write(holder);
+
+ // Writing with a different timezone should throw
+ holder.timezone = "AsdfTimeZone";
+ holder.value = 77777;
+ IllegalArgumentException ex =
+ assertThrows(
+ IllegalArgumentException.class, () -> writer.timeStampNanoTZ().write(holder));
+ assertEquals(
+ "holder.timezone: AsdfTimeZone not equal to vector timezone: SomeFakeTimeZone",
+ ex.getMessage());
+
+ writer.endList();
+ vector.setValueCount(1);
+
+ Field expectedDataField =
+ new Field(
+ BaseRepeatedValueVector.DATA_VECTOR_NAME,
+ FieldType.nullable(new ArrowType.Timestamp(TimeUnit.NANOSECOND, "SomeFakeTimeZone")),
+ null);
+ Field expectedField =
+ new Field(
+ vector.getName(),
+ FieldType.nullable(new ArrowType.FixedSizeList(3)),
+ List.of(expectedDataField));
+
+ assertEquals(expectedField, writer.getField());
+ }
+ }
+
+ @Test
+ public void testWriterUsingHolderTimestampMilliTZField() {
+ try (final FixedSizeListVector vector =
+ FixedSizeListVector.empty("vector", /* size= */ 3, allocator)) {
+ UnionFixedSizeListWriter writer = vector.getWriter();
+ writer.allocate();
+
+ TimeStampMilliTZHolder holder = new TimeStampMilliTZHolder();
+ holder.timezone = "SomeFakeTimeZone";
+ writer.startList();
+ holder.value = 12341234L;
+ writer.timeStampMilliTZ().write(holder);
+ holder.value = 55555L;
+ writer.timeStampMilliTZ().write(holder);
+
+ // Writing with a different timezone should throw
+ holder.timezone = "AsdfTimeZone";
+ holder.value = 77777;
+ IllegalArgumentException ex =
+ assertThrows(
+ IllegalArgumentException.class, () -> writer.timeStampMilliTZ().write(holder));
+ assertEquals(
+ "holder.timezone: AsdfTimeZone not equal to vector timezone: SomeFakeTimeZone",
+ ex.getMessage());
+
+ writer.endList();
+ vector.setValueCount(1);
+
+ Field expectedDataField =
+ new Field(
+ BaseRepeatedValueVector.DATA_VECTOR_NAME,
+ FieldType.nullable(new ArrowType.Timestamp(TimeUnit.MILLISECOND, "SomeFakeTimeZone")),
+ null);
+ Field expectedField =
+ new Field(
+ vector.getName(),
+ FieldType.nullable(new ArrowType.FixedSizeList(3)),
+ List.of(expectedDataField));
+
+ assertEquals(expectedField, writer.getField());
+ }
+ }
+
+ @Test
+ public void testWriterUsingHolderDurationField() {
+ try (final FixedSizeListVector vector =
+ FixedSizeListVector.empty("vector", /* size= */ 3, allocator)) {
+ UnionFixedSizeListWriter writer = vector.getWriter();
+ writer.allocate();
+
+ DurationHolder durationHolder = new DurationHolder();
+ durationHolder.unit = TimeUnit.MILLISECOND;
+
+ writer.startList();
+ durationHolder.value = 812374L;
+ writer.duration().write(durationHolder);
+ durationHolder.value = 143451L;
+ writer.duration().write(durationHolder);
+
+ // Writing with a different unit should throw
+ durationHolder.unit = TimeUnit.SECOND;
+ durationHolder.value = 8888888;
+ IllegalArgumentException ex =
+ assertThrows(
+ IllegalArgumentException.class, () -> writer.duration().write(durationHolder));
+ assertEquals("holder.unit: SECOND not equal to vector unit: MILLISECOND", ex.getMessage());
+
+ writer.endList();
+ vector.setValueCount(1);
+
+ Field expectedDataField =
+ new Field(
+ BaseRepeatedValueVector.DATA_VECTOR_NAME,
+ FieldType.nullable(new ArrowType.Duration(TimeUnit.MILLISECOND)),
+ null);
+ Field expectedField =
+ new Field(
+ vector.getName(),
+ FieldType.nullable(new ArrowType.FixedSizeList(3)),
+ List.of(expectedDataField));
+
+ assertEquals(expectedField, writer.getField());
+ }
+ }
+
+ @Test
+ public void testWriterUsingHolderFixedSizeBinaryField() {
+ try (final FixedSizeListVector vector =
+ FixedSizeListVector.empty("vector", /* size= */ 2, allocator)) {
+ UnionFixedSizeListWriter writer = vector.getWriter();
+ writer.allocate();
+
+ FixedSizeBinaryHolder holder1 =
+ TestUtils.fixedSizeBinaryHolder(allocator, new byte[] {11, 22});
+ FixedSizeBinaryHolder holder2 =
+ TestUtils.fixedSizeBinaryHolder(allocator, new byte[] {32, 21});
+
+ writer.startList();
+ writer.fixedSizeBinary().write(holder1);
+ holder1.buffer.close();
+ writer.fixedSizeBinary().write(holder2);
+ holder2.buffer.close();
+
+ writer.endList();
+ vector.setValueCount(1);
+
+ FieldReader reader = vector.getReader();
+ assertTrue(reader.isSet(), "shouldn't be null");
+
+ Field expectedDataField =
+ new Field(
+ BaseRepeatedValueVector.DATA_VECTOR_NAME,
+ FieldType.nullable(new ArrowType.FixedSizeBinary(2)),
+ null);
+ Field expectedField =
+ new Field(
+ vector.getName(),
+ FieldType.nullable(new ArrowType.FixedSizeList(2)),
+ List.of(expectedDataField));
+
+ assertEquals(expectedField, writer.getField());
+ }
+ }
+
private int[] convertListToIntArray(List> list) {
int[] values = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java b/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java
index 101d942d2a..bf9bba9c78 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java
@@ -16,6 +16,7 @@
*/
package org.apache.arrow.vector;
+import static org.apache.arrow.vector.BitVectorHelper.getValidityBufferSizeFromCount;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -25,18 +26,24 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.UUID;
import org.apache.arrow.memory.ArrowBuf;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.vector.complex.BaseRepeatedValueVector;
import org.apache.arrow.vector.complex.LargeListVector;
import org.apache.arrow.vector.complex.ListVector;
+import org.apache.arrow.vector.complex.impl.UnionLargeListReader;
import org.apache.arrow.vector.complex.impl.UnionLargeListWriter;
import org.apache.arrow.vector.complex.reader.FieldReader;
+import org.apache.arrow.vector.complex.writer.BaseWriter.ExtensionWriter;
+import org.apache.arrow.vector.extension.UuidType;
+import org.apache.arrow.vector.holders.NullableUuidHolder;
import org.apache.arrow.vector.types.Types.MinorType;
import org.apache.arrow.vector.types.pojo.ArrowType;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.FieldType;
import org.apache.arrow.vector.util.TransferPair;
+import org.apache.arrow.vector.util.UuidUtility;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -943,7 +950,7 @@ public void testGetBufferSizeFor() {
int[] indices = new int[] {0, 2, 4, 6, 10, 14};
for (int valueCount = 1; valueCount <= 5; valueCount++) {
- int validityBufferSize = BitVectorHelper.getValidityBufferSize(valueCount);
+ int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
int offsetBufferSize = (valueCount + 1) * LargeListVector.OFFSET_WIDTH;
int expectedSize =
@@ -1020,6 +1027,99 @@ public void testGetTransferPairWithField() throws Exception {
}
}
+ @Test
+ public void testCopyValueSafeForExtensionType() throws Exception {
+ try (LargeListVector inVector = LargeListVector.empty("input", allocator);
+ LargeListVector outVector = LargeListVector.empty("output", allocator)) {
+ UnionLargeListWriter writer = inVector.getWriter();
+ writer.allocate();
+
+ // Create first list with UUIDs
+ writer.setPosition(0);
+ UUID u1 = UUID.randomUUID();
+ UUID u2 = UUID.randomUUID();
+ writer.startList();
+ ExtensionWriter extensionWriter = writer.extension(UuidType.INSTANCE);
+ extensionWriter.writeExtension(u1);
+ extensionWriter.writeExtension(u2);
+ writer.endList();
+
+ // Create second list with UUIDs
+ writer.setPosition(1);
+ UUID u3 = UUID.randomUUID();
+ UUID u4 = UUID.randomUUID();
+ writer.startList();
+ extensionWriter = writer.extension(UuidType.INSTANCE);
+ extensionWriter.writeExtension(u3);
+ extensionWriter.writeExtension(u4);
+ extensionWriter.writeNull();
+
+ writer.endList();
+ writer.setValueCount(2);
+
+ // Use copyFromSafe with ExtensionTypeWriterFactory
+ // This internally calls TransferImpl.copyValueSafe with ExtensionTypeWriterFactory
+ outVector.allocateNew();
+ TransferPair tp = inVector.makeTransferPair(outVector);
+ tp.copyValueSafe(0, 0);
+ tp.copyValueSafe(1, 1);
+ outVector.setValueCount(2);
+
+ // Verify first list
+ UnionLargeListReader reader = outVector.getReader();
+ reader.setPosition(0);
+ assertTrue(reader.isSet(), "first list shouldn't be null");
+ reader.next();
+ FieldReader uuidReader = reader.reader();
+ NullableUuidHolder holder = new NullableUuidHolder();
+ uuidReader.read(holder);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u1, actualUuid);
+ reader.next();
+ uuidReader = reader.reader();
+ uuidReader.read(holder);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u2, actualUuid);
+
+ // Verify second list
+ reader.setPosition(1);
+ assertTrue(reader.isSet(), "second list shouldn't be null");
+ reader.next();
+ uuidReader = reader.reader();
+ uuidReader.read(holder);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u3, actualUuid);
+ reader.next();
+ uuidReader = reader.reader();
+ uuidReader.read(holder);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u4, actualUuid);
+ reader.next();
+ uuidReader = reader.reader();
+ assertFalse(uuidReader.isSet(), "third element should be null");
+ }
+ }
+
+ @Test
+ public void testEmptyLargeListOffsetBuffer() {
+ // Test that LargeListVector has correct readableBytes after allocation.
+ // According to Arrow spec, offset buffer must have N+1 entries.
+ // Even when N=0, it should contain [0].
+ try (LargeListVector list = LargeListVector.empty("list", allocator)) {
+ list.addOrGetVector(FieldType.nullable(MinorType.INT.getType()));
+ list.allocateNew();
+ list.setValueCount(0);
+
+ List buffers = list.getFieldBuffers();
+ assertTrue(
+ buffers.get(1).readableBytes() >= LargeListVector.OFFSET_WIDTH,
+ "Offset buffer should have at least "
+ + LargeListVector.OFFSET_WIDTH
+ + " bytes for offset[0]");
+ assertEquals(0L, list.getOffsetBuffer().getLong(0));
+ }
+ }
+
private void writeIntValues(UnionLargeListWriter writer, int[] values) {
writer.startList();
for (int v : values) {
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestLargeListViewVector.java b/vector/src/test/java/org/apache/arrow/vector/TestLargeListViewVector.java
index 26e7bb4a0d..256aa99687 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestLargeListViewVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestLargeListViewVector.java
@@ -16,6 +16,7 @@
*/
package org.apache.arrow.vector;
+import static org.apache.arrow.vector.BitVectorHelper.getValidityBufferSizeFromCount;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
@@ -1062,7 +1063,7 @@ public void testGetBufferSizeFor() {
int[] indices = new int[] {0, 2, 4, 6, 10, 14};
for (int valueCount = 1; valueCount <= 5; valueCount++) {
- int validityBufferSize = BitVectorHelper.getValidityBufferSize(valueCount);
+ int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
int offsetBufferSize = valueCount * BaseLargeRepeatedValueViewVector.OFFSET_WIDTH;
int sizeBufferSize = valueCount * BaseLargeRepeatedValueViewVector.SIZE_WIDTH;
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java
index 1d6fa39f9e..0c90b32abc 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java
@@ -16,6 +16,7 @@
*/
package org.apache.arrow.vector;
+import static org.apache.arrow.vector.BitVectorHelper.getValidityBufferSizeFromCount;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -26,15 +27,20 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.UUID;
import org.apache.arrow.memory.ArrowBuf;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.util.AutoCloseables;
import org.apache.arrow.vector.complex.BaseRepeatedValueVector;
import org.apache.arrow.vector.complex.ListVector;
+import org.apache.arrow.vector.complex.impl.UnionListReader;
import org.apache.arrow.vector.complex.impl.UnionListWriter;
import org.apache.arrow.vector.complex.reader.FieldReader;
+import org.apache.arrow.vector.complex.writer.BaseWriter.ExtensionWriter;
+import org.apache.arrow.vector.extension.UuidType;
import org.apache.arrow.vector.holders.DurationHolder;
import org.apache.arrow.vector.holders.FixedSizeBinaryHolder;
+import org.apache.arrow.vector.holders.NullableUuidHolder;
import org.apache.arrow.vector.holders.TimeStampMilliTZHolder;
import org.apache.arrow.vector.types.TimeUnit;
import org.apache.arrow.vector.types.Types.MinorType;
@@ -42,6 +48,7 @@
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.FieldType;
import org.apache.arrow.vector.util.TransferPair;
+import org.apache.arrow.vector.util.UuidUtility;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -1123,7 +1130,7 @@ public void testGetBufferSizeFor() {
int[] indices = new int[] {0, 2, 4, 6, 10, 14};
for (int valueCount = 1; valueCount <= 5; valueCount++) {
- int validityBufferSize = BitVectorHelper.getValidityBufferSize(valueCount);
+ int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
int offsetBufferSize = (valueCount + 1) * BaseRepeatedValueVector.OFFSET_WIDTH;
int expectedSize =
@@ -1198,6 +1205,200 @@ public void testGetTransferPairWithField() {
}
}
+ @Test
+ public void testListVectorWithExtensionType() throws Exception {
+ final FieldType type = FieldType.nullable(UuidType.INSTANCE);
+ try (final ListVector inVector = new ListVector("list", allocator, type, null)) {
+ UnionListWriter writer = inVector.getWriter();
+ writer.allocate();
+ writer.setPosition(0);
+ UUID u1 = UUID.randomUUID();
+ UUID u2 = UUID.randomUUID();
+ writer.startList();
+ ExtensionWriter extensionWriter = writer.extension(UuidType.INSTANCE);
+ extensionWriter.writeExtension(u1);
+ extensionWriter.writeExtension(u2);
+ writer.endList();
+
+ writer.setValueCount(1);
+
+ FieldReader reader = inVector.getReader();
+ assertTrue(reader.isSet(), "shouldn't be null");
+ Object result = inVector.getObject(0);
+ ArrayList resultSet = (ArrayList) result;
+ assertEquals(2, resultSet.size());
+ assertEquals(u1, resultSet.get(0));
+ assertEquals(u2, resultSet.get(1));
+ }
+ }
+
+ @Test
+ public void testListVectorReaderForExtensionType() throws Exception {
+ final FieldType type = FieldType.nullable(UuidType.INSTANCE);
+ try (final ListVector inVector = new ListVector("list", allocator, type, null)) {
+ UnionListWriter writer = inVector.getWriter();
+ writer.allocate();
+ writer.setPosition(0);
+ UUID u1 = UUID.randomUUID();
+ UUID u2 = UUID.randomUUID();
+ writer.startList();
+ ExtensionWriter extensionWriter = writer.extension(UuidType.INSTANCE);
+ extensionWriter.writeExtension(u1);
+ extensionWriter.writeExtension(u2);
+ writer.endList();
+
+ writer.setValueCount(1);
+
+ UnionListReader reader = inVector.getReader();
+ assertTrue(reader.isSet(), "shouldn't be null");
+ reader.setPosition(0);
+ reader.next();
+ FieldReader uuidReader = reader.reader();
+ NullableUuidHolder holder = new NullableUuidHolder();
+ uuidReader.read(holder);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u1, actualUuid);
+ reader.next();
+ uuidReader = reader.reader();
+ uuidReader.read(holder);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u2, actualUuid);
+ }
+ }
+
+ @Test
+ public void testCopyFromForExtensionType() throws Exception {
+ try (ListVector inVector = ListVector.empty("input", allocator);
+ ListVector outVector = ListVector.empty("output", allocator)) {
+ UnionListWriter writer = inVector.getWriter();
+ writer.allocate();
+ writer.setPosition(0);
+ UUID u1 = UUID.randomUUID();
+ UUID u2 = UUID.randomUUID();
+ writer.startList();
+
+ writer.extension(UuidType.INSTANCE).writeExtension(u1);
+ writer.writeExtension(u2);
+ writer.writeNull();
+ writer.endList();
+
+ writer.setValueCount(3);
+
+ // copy values from input to output
+ outVector.allocateNew();
+ outVector.copyFrom(0, 0, inVector);
+ outVector.setValueCount(3);
+
+ UnionListReader reader = outVector.getReader();
+ assertTrue(reader.isSet(), "shouldn't be null");
+ reader.setPosition(0);
+ reader.next();
+ FieldReader uuidReader = reader.reader();
+ NullableUuidHolder holder = new NullableUuidHolder();
+ uuidReader.read(holder);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u1, actualUuid);
+ reader.next();
+ uuidReader = reader.reader();
+ uuidReader.read(holder);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u2, actualUuid);
+ }
+ }
+
+ @Test
+ public void testCopyValueSafeForExtensionType() throws Exception {
+ try (ListVector inVector = ListVector.empty("input", allocator);
+ ListVector outVector = ListVector.empty("output", allocator)) {
+ UnionListWriter writer = inVector.getWriter();
+ writer.allocate();
+
+ // Create first list with UUIDs
+ writer.setPosition(0);
+ UUID u1 = UUID.randomUUID();
+ UUID u2 = UUID.randomUUID();
+ writer.startList();
+ ExtensionWriter extensionWriter = writer.extension(UuidType.INSTANCE);
+ extensionWriter.writeExtension(u1);
+ extensionWriter.writeExtension(u2);
+ writer.endList();
+
+ // Create second list with UUIDs
+ writer.setPosition(1);
+ UUID u3 = UUID.randomUUID();
+ UUID u4 = UUID.randomUUID();
+ writer.startList();
+ extensionWriter = writer.extension(UuidType.INSTANCE);
+ extensionWriter.writeExtension(u3);
+ extensionWriter.writeExtension(u4);
+ extensionWriter.writeNull();
+
+ writer.endList();
+ writer.setValueCount(2);
+
+ // Use TransferPair with ExtensionTypeWriterFactory
+ // This tests the new makeTransferPair API with writerFactory parameter
+ outVector.allocateNew();
+ TransferPair transferPair = inVector.makeTransferPair(outVector);
+ transferPair.copyValueSafe(0, 0);
+ transferPair.copyValueSafe(1, 1);
+ outVector.setValueCount(2);
+
+ // Verify first list
+ UnionListReader reader = outVector.getReader();
+ reader.setPosition(0);
+ assertTrue(reader.isSet(), "first list shouldn't be null");
+ reader.next();
+ FieldReader uuidReader = reader.reader();
+ NullableUuidHolder holder = new NullableUuidHolder();
+ uuidReader.read(holder);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u1, actualUuid);
+ reader.next();
+ uuidReader = reader.reader();
+ uuidReader.read(holder);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u2, actualUuid);
+
+ // Verify second list
+ reader.setPosition(1);
+ assertTrue(reader.isSet(), "second list shouldn't be null");
+ reader.next();
+ uuidReader = reader.reader();
+ uuidReader.read(holder);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u3, actualUuid);
+ reader.next();
+ uuidReader = reader.reader();
+ uuidReader.read(holder);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u4, actualUuid);
+ reader.next();
+ uuidReader = reader.reader();
+ assertFalse(uuidReader.isSet(), "third element should be null");
+ }
+ }
+
+ @Test
+ public void testEmptyListOffsetBuffer() {
+ // Test that ListVector has correct readableBytes after allocation.
+ // According to Arrow spec, offset buffer must have N+1 entries.
+ // Even when N=0, it should contain [0].
+ try (ListVector list = ListVector.empty("list", allocator)) {
+ list.addOrGetVector(FieldType.nullable(MinorType.INT.getType()));
+ list.allocateNew();
+ list.setValueCount(0);
+
+ List buffers = list.getFieldBuffers();
+ assertTrue(
+ buffers.get(1).readableBytes() >= BaseRepeatedValueVector.OFFSET_WIDTH,
+ "Offset buffer should have at least "
+ + BaseRepeatedValueVector.OFFSET_WIDTH
+ + " bytes for offset[0]");
+ assertEquals(0, list.getOffsetBuffer().getInt(0));
+ }
+ }
+
private void writeIntValues(UnionListWriter writer, int[] values) {
writer.startList();
for (int v : values) {
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestListViewVector.java b/vector/src/test/java/org/apache/arrow/vector/TestListViewVector.java
index 639585fc48..8ab0edb145 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestListViewVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestListViewVector.java
@@ -16,6 +16,7 @@
*/
package org.apache.arrow.vector;
+import static org.apache.arrow.vector.BitVectorHelper.getValidityBufferSizeFromCount;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -1075,7 +1076,7 @@ public void testGetBufferSizeFor() {
int[] indices = new int[] {0, 2, 4, 6, 10, 14};
for (int valueCount = 1; valueCount <= 5; valueCount++) {
- int validityBufferSize = BitVectorHelper.getValidityBufferSize(valueCount);
+ int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
int offsetBufferSize = valueCount * BaseRepeatedValueViewVector.OFFSET_WIDTH;
int sizeBufferSize = valueCount * BaseRepeatedValueViewVector.SIZE_WIDTH;
@@ -1549,55 +1550,7 @@ public void testOverwriteWithNull() {
public void testOutOfOrderOffset1() {
// [[12, -7, 25], null, [0, -127, 127, 50], [], [50, 12]]
try (ListViewVector listViewVector = ListViewVector.empty("listview", allocator)) {
- // Allocate buffers in listViewVector by calling `allocateNew` method.
- listViewVector.allocateNew();
-
- // Initialize the child vector using `initializeChildrenFromFields` method.
-
- FieldType fieldType = new FieldType(true, new ArrowType.Int(16, true), null, null);
- Field field = new Field("child-vector", fieldType, null);
- listViewVector.initializeChildrenFromFields(Collections.singletonList(field));
-
- // Set values in the child vector.
- FieldVector fieldVector = listViewVector.getDataVector();
- fieldVector.clear();
-
- SmallIntVector childVector = (SmallIntVector) fieldVector;
-
- childVector.allocateNew(7);
-
- childVector.set(0, 0);
- childVector.set(1, -127);
- childVector.set(2, 127);
- childVector.set(3, 50);
- childVector.set(4, 12);
- childVector.set(5, -7);
- childVector.set(6, 25);
-
- childVector.setValueCount(7);
-
- // Set validity, offset and size buffers using `setValidity`,
- // `setOffset` and `setSize` methods.
- listViewVector.setValidity(0, 1);
- listViewVector.setValidity(1, 0);
- listViewVector.setValidity(2, 1);
- listViewVector.setValidity(3, 1);
- listViewVector.setValidity(4, 1);
-
- listViewVector.setOffset(0, 4);
- listViewVector.setOffset(1, 7);
- listViewVector.setOffset(2, 0);
- listViewVector.setOffset(3, 0);
- listViewVector.setOffset(4, 3);
-
- listViewVector.setSize(0, 3);
- listViewVector.setSize(1, 0);
- listViewVector.setSize(2, 4);
- listViewVector.setSize(3, 0);
- listViewVector.setSize(4, 2);
-
- // Set value count using `setValueCount` method.
- listViewVector.setValueCount(5);
+ initializeListViewVectorAsInSpecification(listViewVector);
final ArrowBuf offSetBuffer = listViewVector.getOffsetBuffer();
final ArrowBuf sizeBuffer = listViewVector.getSizeBuffer();
@@ -2216,6 +2169,105 @@ public void testRangeChildVector2() {
}
}
+ @Test
+ public void testGetElementStartIndexAndEndIndexOrderedOffsetsNoIntersection() {
+ /*
+ values = [10, 20, 30, 40, 50]
+ offsets = [0, 3]
+ sizes = [3, 2]
+ vector: [[10, 20, 30], [40, 50]]
+ */
+ try (ListViewVector listViewVector = ListViewVector.empty("sourceVector", allocator)) {
+ initializeListViewVector(
+ listViewVector, List.of(10, 20, 30, 40, 50), List.of(1, 1), List.of(0, 3), List.of(3, 2));
+
+ assertEquals(0, listViewVector.getElementStartIndex(0));
+ assertEquals(3, listViewVector.getElementEndIndex(0));
+ assertEquals(3, listViewVector.getElementStartIndex(1));
+ assertEquals(5, listViewVector.getElementEndIndex(1));
+
+ final FieldVector dataVec = listViewVector.getDataVector();
+ int elemIndex = 0;
+ int start = listViewVector.getElementStartIndex(elemIndex);
+ int end = listViewVector.getElementEndIndex(elemIndex);
+ List> list = listViewVector.getObject(elemIndex);
+ assertEquals(end - start, list.size());
+ for (int j = 0; j < list.size(); j++) {
+ assertEquals(((SmallIntVector) dataVec).get(start + j), list.get(j));
+ }
+ }
+ }
+
+ @Test
+ public void testGetElementStartIndexAndEndIndexNotOrderedOffsetsNoIntersection() {
+ /*
+ values = [1, 2, 3, 4, 5, 6]
+ validity = [1, 1, 1]
+ offsets = [4, 2, 0]
+ sizes = [2, 2, 2]
+ vector: [[5, 6], [3, 4], [1, 2]]
+ */
+ try (ListViewVector listViewVector = ListViewVector.empty("sourceVector", allocator)) {
+ initializeListViewVector(
+ listViewVector,
+ List.of(1, 2, 3, 4, 5, 6),
+ List.of(1, 1, 1),
+ List.of(4, 2, 0),
+ List.of(2, 2, 2));
+
+ assertEquals(4, listViewVector.getElementStartIndex(0));
+ assertEquals(6, listViewVector.getElementEndIndex(0));
+ assertEquals(2, listViewVector.getElementStartIndex(1));
+ assertEquals(4, listViewVector.getElementEndIndex(1));
+ assertEquals(0, listViewVector.getElementStartIndex(2));
+ assertEquals(2, listViewVector.getElementEndIndex(2));
+ }
+ }
+
+ @Test
+ public void testGetElementStartIndexAndEndIndexOrderedOffsetsWithIntersection() {
+ /*
+ values = [1, 2, 3, 4, 5]
+ validity = [1, 1, 1]
+ offsets = [0, 1, 4]
+ sizes = [2, 3, 1]
+ vector: [[1, 2], [2, 3, 4], [5]]
+ */
+ try (ListViewVector listViewVector = ListViewVector.empty("sourceVector", allocator)) {
+ initializeListViewVector(
+ listViewVector,
+ List.of(1, 2, 3, 4, 5),
+ List.of(1, 1, 1),
+ List.of(0, 1, 4),
+ List.of(2, 3, 1));
+
+ assertEquals(0, listViewVector.getElementStartIndex(0));
+ assertEquals(2, listViewVector.getElementEndIndex(0));
+ assertEquals(1, listViewVector.getElementStartIndex(1));
+ assertEquals(4, listViewVector.getElementEndIndex(1));
+ assertEquals(4, listViewVector.getElementStartIndex(2));
+ assertEquals(5, listViewVector.getElementEndIndex(2));
+ }
+ }
+
+ @Test
+ public void testGetElementStartIndexAndEndIndexOrderedOffsetsAsInSpecification() {
+ try (ListViewVector listViewVector = ListViewVector.empty("sourceVector", allocator)) {
+ initializeListViewVectorAsInSpecification(listViewVector);
+
+ assertEquals(4, listViewVector.getElementStartIndex(0));
+ assertEquals(7, listViewVector.getElementEndIndex(0));
+ assertEquals(7, listViewVector.getElementStartIndex(1));
+ assertEquals(7, listViewVector.getElementEndIndex(1));
+ assertEquals(0, listViewVector.getElementStartIndex(2));
+ assertEquals(4, listViewVector.getElementEndIndex(2));
+ assertEquals(0, listViewVector.getElementStartIndex(3));
+ assertEquals(0, listViewVector.getElementEndIndex(3));
+ assertEquals(3, listViewVector.getElementStartIndex(4));
+ assertEquals(5, listViewVector.getElementEndIndex(4));
+ }
+ }
+
private void writeIntValues(UnionListViewWriter writer, int[] values) {
writer.startListView();
for (int v : values) {
@@ -2223,4 +2275,70 @@ private void writeIntValues(UnionListViewWriter writer, int[] values) {
}
writer.endListView();
}
+
+ /**
+ * ListViewVector from the specification.
+ */
+ private void initializeListViewVectorAsInSpecification(ListViewVector listViewVector) {
+ /*
+ values = [0, -127, 127, 50, 12, -7, 25]
+ validity = [1, 1, 1, 0, 1] (reversed)
+ offsets = [4, 7, 0, 0, 3]
+ sizes = [3, 0, 4, 0, 2]
+ vector: [[12, -7, 25], null, [0, -127, 127, 50], [], [50, 12]]
+ */
+ initializeListViewVector(
+ listViewVector,
+ List.of(0, -127, 127, 50, 12, -7, 25),
+ List.of(1, 1, 1, 0, 1),
+ List.of(4, 7, 0, 0, 3),
+ List.of(3, 0, 4, 0, 2));
+ }
+
+ private void initializeListViewVector(
+ ListViewVector listViewVector,
+ List values,
+ List validity,
+ List offsets,
+ List sizes) {
+ // Allocate buffers in listViewVector by calling `allocateNew` method.
+ assert offsets.size() == sizes.size();
+ listViewVector.allocateNew();
+
+ // Initialize the child vector using `initializeChildrenFromFields` method.
+ FieldType fieldType = new FieldType(true, new ArrowType.Int(16, true), null, null);
+ Field field = new Field("child-vector", fieldType, null);
+ listViewVector.initializeChildrenFromFields(Collections.singletonList(field));
+
+ // Set values in the child vector.
+ FieldVector fieldVector = listViewVector.getDataVector();
+ fieldVector.clear();
+
+ SmallIntVector childVector = (SmallIntVector) fieldVector;
+ childVector.allocateNew(values.size());
+ for (int i = 0; i < values.size(); i++) {
+ childVector.set(i, values.get(i));
+ }
+ childVector.setValueCount(values.size());
+
+ // Set validity, offset and size buffers using `setValidity`,
+ // `setOffset` and `setSize` methods.
+ List reversedValidity = new ArrayList<>(validity);
+ Collections.reverse(reversedValidity);
+ for (int i = 0; i < reversedValidity.size(); i++) {
+ listViewVector.setValidity(i, reversedValidity.get(i));
+ }
+
+ for (int i = 0; i < offsets.size(); i++) {
+ listViewVector.setOffset(i, offsets.get(i));
+ }
+
+ for (int i = 0; i < sizes.size(); i++) {
+ listViewVector.setSize(i, sizes.get(i));
+ }
+
+ // Set value count using `setValueCount` method.
+ listViewVector.setValueCount(offsets.size());
+ }
}
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestMapVector.java b/vector/src/test/java/org/apache/arrow/vector/TestMapVector.java
index 313d83ec91..2f520f3882 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestMapVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestMapVector.java
@@ -16,16 +16,19 @@
*/
package org.apache.arrow.vector;
+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.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import org.apache.arrow.memory.ArrowBuf;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.vector.complex.MapVector;
@@ -33,15 +36,20 @@
import org.apache.arrow.vector.complex.impl.UnionMapReader;
import org.apache.arrow.vector.complex.impl.UnionMapWriter;
import org.apache.arrow.vector.complex.reader.FieldReader;
+import org.apache.arrow.vector.complex.writer.BaseWriter.ExtensionWriter;
import org.apache.arrow.vector.complex.writer.BaseWriter.ListWriter;
import org.apache.arrow.vector.complex.writer.BaseWriter.MapWriter;
import org.apache.arrow.vector.complex.writer.FieldWriter;
+import org.apache.arrow.vector.extension.UuidType;
+import org.apache.arrow.vector.holders.FixedSizeBinaryHolder;
+import org.apache.arrow.vector.holders.NullableUuidHolder;
import org.apache.arrow.vector.types.Types.MinorType;
import org.apache.arrow.vector.types.pojo.ArrowType;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.FieldType;
import org.apache.arrow.vector.util.JsonStringArrayList;
import org.apache.arrow.vector.util.TransferPair;
+import org.apache.arrow.vector.util.UuidUtility;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -1263,4 +1271,395 @@ public void testMapTypeReturnsSupportedMapWriter() {
assertEquals(11, getResultValue(resultStruct));
}
}
+
+ @Test
+ public void testMapVectorWithExtensionType() throws Exception {
+ try (final MapVector inVector = MapVector.empty("map", allocator, false)) {
+ inVector.allocateNew();
+ UnionMapWriter writer = inVector.getWriter();
+ writer.setPosition(0);
+ UUID u1 = UUID.randomUUID();
+ UUID u2 = UUID.randomUUID();
+ writer.startMap();
+ writer.startEntry();
+ writer.key().bigInt().writeBigInt(0);
+ ExtensionWriter extensionWriter = writer.value().extension(UuidType.INSTANCE);
+ extensionWriter.writeExtension(u1, UuidType.INSTANCE);
+ writer.endEntry();
+ writer.startEntry();
+ writer.key().bigInt().writeBigInt(1);
+ extensionWriter = writer.value().extension(UuidType.INSTANCE);
+ extensionWriter.writeExtension(u2, UuidType.INSTANCE);
+ writer.endEntry();
+ writer.endMap();
+
+ writer.setValueCount(1);
+
+ UnionMapReader mapReader = inVector.getReader();
+ mapReader.setPosition(0);
+ mapReader.next();
+ FieldReader uuidReader = mapReader.value();
+ NullableUuidHolder holder = new NullableUuidHolder();
+ uuidReader.read(holder);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u1, actualUuid);
+ mapReader.next();
+ uuidReader = mapReader.value();
+ uuidReader.read(holder);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u2, actualUuid);
+ }
+ }
+
+ @Test
+ public void testCopyFromForExtensionType() throws Exception {
+ try (final MapVector inVector = MapVector.empty("in", allocator, false);
+ final MapVector outVector = MapVector.empty("out", allocator, false)) {
+ inVector.allocateNew();
+ UnionMapWriter writer = inVector.getWriter();
+ writer.setPosition(0);
+ UUID u1 = UUID.randomUUID();
+ UUID u2 = UUID.randomUUID();
+ writer.startMap();
+ writer.startEntry();
+ writer.key().bigInt().writeBigInt(0);
+ ExtensionWriter extensionWriter = writer.value().extension(UuidType.INSTANCE);
+ extensionWriter.writeExtension(u1, UuidType.INSTANCE);
+ writer.endEntry();
+ writer.startEntry();
+ writer.key().bigInt().writeBigInt(1);
+ extensionWriter.writeExtension(u2, UuidType.INSTANCE);
+ writer.endEntry();
+ writer.endMap();
+
+ writer.setValueCount(1);
+ outVector.allocateNew();
+ outVector.copyFrom(0, 0, inVector);
+ outVector.setValueCount(1);
+
+ UnionMapReader mapReader = outVector.getReader();
+ mapReader.setPosition(0);
+ mapReader.next();
+ FieldReader uuidReader = mapReader.value();
+ NullableUuidHolder holder = new NullableUuidHolder();
+ uuidReader.read(holder);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u1, actualUuid);
+ mapReader.next();
+ uuidReader = mapReader.value();
+ uuidReader.read(holder);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(u2, actualUuid);
+ }
+ }
+
+ /**
+ * Regression test for GH-586: UnionMapWriter.fixedSizeBinary() should properly delegate to the
+ * entry writer for both key and value paths.
+ */
+ @Test
+ public void testFixedSizeBinaryWriter() {
+ try (MapVector mapVector = MapVector.empty("map_vector", allocator, false)) {
+ UnionMapWriter writer = mapVector.getWriter();
+ writer.allocate();
+
+ // populate input vector with the following records
+ // {[11, 22] -> [32, 21]}
+ // {1 -> [11, 22], 2 -> [32, 21]}
+ // null
+ // {[11, 22] -> 1, [32, 21] -> 2}
+ // {[11, 22] -> null}
+ // {null -> [32, 21]} - wrong "for a given entry, the "key" is non-nullable" - todo: it
+ // shouldn't work. Should it?
+ FixedSizeBinaryHolder holder1 =
+ TestUtils.fixedSizeBinaryHolder(allocator, new byte[] {11, 22});
+ FixedSizeBinaryHolder holder2 =
+ TestUtils.fixedSizeBinaryHolder(allocator, new byte[] {32, 21});
+
+ writer.setPosition(0); // optional
+ writer.startMap();
+ writer.startEntry();
+ writer
+ .key()
+ .fixedSizeBinary(holder1.byteWidth)
+ .write(holder1); // need to initialize with byteWidth - NPE otherwise
+ writer.value().fixedSizeBinary(holder2.byteWidth).write(holder2);
+ writer.endEntry();
+ holder1.buffer.close();
+ holder2.buffer.close();
+ writer.endMap();
+
+ // {1 -> [11, 22], 2 -> [32, 21]}
+ holder1 = TestUtils.fixedSizeBinaryHolder(allocator, new byte[] {11, 22});
+ holder2 = TestUtils.fixedSizeBinaryHolder(allocator, new byte[] {32, 21});
+ writer.setPosition(1);
+ writer.startMap();
+ writer.startEntry();
+ writer.key().bigInt().writeBigInt(1);
+ writer.value().fixedSizeBinary().write(holder1);
+ writer.endEntry();
+ holder1.buffer.close();
+ writer.startEntry();
+ writer.key().bigInt().writeBigInt(2);
+ writer.value().fixedSizeBinary().write(holder2);
+ writer.endEntry();
+ writer.endMap();
+ holder2.buffer.close();
+
+ // {[11, 22] -> 1, [32, 21] -> 2}
+ holder1 = TestUtils.fixedSizeBinaryHolder(allocator, new byte[] {11, 22});
+ holder2 = TestUtils.fixedSizeBinaryHolder(allocator, new byte[] {32, 21});
+ writer.setPosition(3);
+ writer.startMap();
+ writer.startEntry();
+ writer.key().fixedSizeBinary().write(holder1);
+ writer.value().bigInt().writeBigInt(1);
+ writer.endEntry();
+ holder1.buffer.close();
+ writer.startEntry();
+ writer.key().fixedSizeBinary().write(holder2);
+ writer.value().bigInt().writeBigInt(2);
+ writer.endEntry();
+ writer.endMap();
+ holder2.buffer.close();
+
+ // {[11, 22] -> null}
+ holder1 = TestUtils.fixedSizeBinaryHolder(allocator, new byte[] {11, 22});
+ writer.setPosition(4);
+ writer.startMap();
+ writer.startEntry();
+ writer.key().fixedSizeBinary().write(holder1);
+ writer.endEntry();
+ writer.endMap();
+ holder1.buffer.close();
+
+ // {null -> [32, 21]}
+ holder2 = TestUtils.fixedSizeBinaryHolder(allocator, new byte[] {32, 21});
+ writer.setPosition(5);
+ writer.startMap();
+ writer.startEntry();
+ writer.value().fixedSizeBinary().write(holder2);
+ writer.endEntry();
+ writer.endMap();
+ holder2.buffer.close();
+
+ writer.setValueCount(6);
+
+ // assert the output vector is correct
+ FieldReader reader = mapVector.getReader();
+ assertTrue(reader.isSet(), "shouldn't be null");
+ reader.setPosition(1);
+ assertTrue(reader.isSet(), "shouldn't be null");
+ reader.setPosition(2);
+ assertFalse(reader.isSet(), "should be null");
+ reader.setPosition(3);
+ assertTrue(reader.isSet(), "shouldn't be null");
+ reader.setPosition(4);
+ assertTrue(reader.isSet(), "shouldn't be null");
+ reader.setPosition(5);
+ assertTrue(reader.isSet(), "shouldn't be null");
+
+ /* index 0 */
+ Object result = mapVector.getObject(0);
+ ArrayList> resultSet = (ArrayList>) result;
+ assertEquals(1, resultSet.size());
+ Map, ?> resultStruct = (Map, ?>) resultSet.get(0);
+ assertTrue(resultStruct.containsKey(MapVector.KEY_NAME));
+ assertTrue(resultStruct.containsKey(MapVector.VALUE_NAME));
+ assertArrayEquals(new byte[] {11, 22}, (byte[]) resultStruct.get(MapVector.KEY_NAME));
+ assertArrayEquals(new byte[] {32, 21}, (byte[]) resultStruct.get(MapVector.VALUE_NAME));
+
+ /* index 1 */
+ result = mapVector.getObject(1);
+ resultSet = (ArrayList>) result;
+ assertEquals(2, resultSet.size());
+ resultStruct = (Map, ?>) resultSet.get(0);
+ assertEquals(1L, getResultKey(resultStruct));
+ assertTrue(resultStruct.containsKey(MapVector.VALUE_NAME));
+ assertArrayEquals(new byte[] {11, 22}, (byte[]) resultStruct.get(MapVector.VALUE_NAME));
+ resultStruct = (Map, ?>) resultSet.get(1);
+ assertEquals(2L, getResultKey(resultStruct));
+ assertTrue(resultStruct.containsKey(MapVector.VALUE_NAME));
+ assertArrayEquals(new byte[] {32, 21}, (byte[]) resultStruct.get(MapVector.VALUE_NAME));
+
+ /* index 2 */
+ result = mapVector.getObject(2);
+ assertNull(result);
+
+ /* index 3 */
+ result = mapVector.getObject(3);
+ resultSet = (ArrayList>) result;
+ assertEquals(2, resultSet.size());
+ resultStruct = (Map, ?>) resultSet.get(0);
+ assertTrue(resultStruct.containsKey(MapVector.KEY_NAME));
+ assertArrayEquals(new byte[] {11, 22}, (byte[]) resultStruct.get(MapVector.KEY_NAME));
+ assertEquals(1L, getResultValue(resultStruct));
+ resultStruct = (Map, ?>) resultSet.get(1);
+ assertTrue(resultStruct.containsKey(MapVector.KEY_NAME));
+ assertArrayEquals(new byte[] {32, 21}, (byte[]) resultStruct.get(MapVector.KEY_NAME));
+ assertEquals(2L, getResultValue(resultStruct));
+
+ /* index 4 */
+ result = mapVector.getObject(4);
+ resultSet = (ArrayList>) result;
+ assertEquals(1, resultSet.size());
+ resultStruct = (Map, ?>) resultSet.get(0);
+ assertTrue(resultStruct.containsKey(MapVector.KEY_NAME));
+ assertArrayEquals(new byte[] {11, 22}, (byte[]) resultStruct.get(MapVector.KEY_NAME));
+ assertFalse(resultStruct.containsKey(MapVector.VALUE_NAME));
+
+ /* index 5 */
+ result = mapVector.getObject(5);
+ resultSet = (ArrayList>) result;
+ assertEquals(1, resultSet.size());
+ resultStruct = (Map, ?>) resultSet.get(0);
+ assertFalse(resultStruct.containsKey(MapVector.KEY_NAME));
+ assertTrue(resultStruct.containsKey(MapVector.VALUE_NAME));
+ assertArrayEquals(new byte[] {32, 21}, (byte[]) resultStruct.get(MapVector.VALUE_NAME));
+ }
+ }
+
+ @Test
+ public void testFixedSizeBinaryFirstInitialization() {
+ try (MapVector mapVector = MapVector.empty("map_vector", allocator, false)) {
+ UnionMapWriter writer = mapVector.getWriter();
+ writer.allocate();
+
+ // populate input vector with the following records
+ // {[11, 22] -> [32, 21]}
+ FixedSizeBinaryHolder holder1 =
+ TestUtils.fixedSizeBinaryHolder(allocator, new byte[] {11, 22});
+ FixedSizeBinaryHolder holder2 =
+ TestUtils.fixedSizeBinaryHolder(allocator, new byte[] {32, 21});
+
+ writer.setPosition(0); // optional
+ writer.startMap();
+ writer.startEntry();
+ // require byteWidth parameter for first-time initialization of `key` or `value` writers
+ assertThrows(NullPointerException.class, () -> writer.key().fixedSizeBinary().write(holder1));
+ assertThrows(
+ NullPointerException.class, () -> writer.value().fixedSizeBinary().write(holder2));
+ writer.key().fixedSizeBinary(holder1.byteWidth).write(holder1);
+ writer.value().fixedSizeBinary(holder2.byteWidth).write(holder2);
+ writer.endEntry();
+ holder1.buffer.close();
+ holder2.buffer.close();
+ writer.endMap();
+
+ writer.setValueCount(1);
+
+ // assert the output vector is correct
+ FieldReader reader = mapVector.getReader();
+ assertTrue(reader.isSet(), "shouldn't be null");
+
+ /* index 0 */
+ Object result = mapVector.getObject(0);
+ ArrayList> resultSet = (ArrayList>) result;
+ assertEquals(1, resultSet.size());
+ Map, ?> resultStruct = (Map, ?>) resultSet.get(0);
+ assertTrue(resultStruct.containsKey(MapVector.KEY_NAME));
+ assertTrue(resultStruct.containsKey(MapVector.VALUE_NAME));
+ assertArrayEquals(new byte[] {11, 22}, (byte[]) resultStruct.get(MapVector.KEY_NAME));
+ assertArrayEquals(new byte[] {32, 21}, (byte[]) resultStruct.get(MapVector.VALUE_NAME));
+ }
+ }
+
+ @Test
+ public void testMapWithUuidKeyAndListUuidValue() throws Exception {
+ try (final MapVector mapVector = MapVector.empty("map", allocator, false)) {
+ mapVector.allocateNew();
+ UnionMapWriter writer = mapVector.getWriter();
+
+ // Create test UUIDs
+ UUID key1 = UUID.randomUUID();
+ UUID key2 = UUID.randomUUID();
+ UUID value1a = UUID.randomUUID();
+ UUID value1b = UUID.randomUUID();
+ UUID value2a = UUID.randomUUID();
+ UUID value2b = UUID.randomUUID();
+ UUID value2c = UUID.randomUUID();
+
+ // Write first map entry: {key1 -> [value1a, value1b]}
+ writer.setPosition(0);
+ writer.startMap();
+
+ writer.startEntry();
+ ExtensionWriter keyWriter = writer.key().extension(UuidType.INSTANCE);
+ keyWriter.writeExtension(key1, UuidType.INSTANCE);
+ ListWriter valueWriter = writer.value().list();
+ valueWriter.startList();
+ ExtensionWriter listItemWriter = valueWriter.extension(UuidType.INSTANCE);
+ listItemWriter.writeExtension(value1a, UuidType.INSTANCE);
+ listItemWriter = valueWriter.extension(UuidType.INSTANCE);
+ listItemWriter.writeExtension(value1b, UuidType.INSTANCE);
+ valueWriter.endList();
+ writer.endEntry();
+
+ writer.startEntry();
+ keyWriter = writer.key().extension(UuidType.INSTANCE);
+ keyWriter.writeExtension(key2, UuidType.INSTANCE);
+ valueWriter = writer.value().list();
+ valueWriter.startList();
+ listItemWriter = valueWriter.extension(UuidType.INSTANCE);
+ listItemWriter.writeExtension(value2a, UuidType.INSTANCE);
+ listItemWriter = valueWriter.extension(UuidType.INSTANCE);
+ listItemWriter.writeExtension(value2b, UuidType.INSTANCE);
+ listItemWriter = valueWriter.extension(UuidType.INSTANCE);
+ listItemWriter.writeExtension(value2c, UuidType.INSTANCE);
+ valueWriter.endList();
+ writer.endEntry();
+
+ writer.endMap();
+ writer.setValueCount(1);
+
+ // Read and verify the data
+ UnionMapReader mapReader = mapVector.getReader();
+ mapReader.setPosition(0);
+
+ // Read first entry
+ mapReader.next();
+ FieldReader keyReader = mapReader.key();
+ NullableUuidHolder keyHolder = new NullableUuidHolder();
+ keyReader.read(keyHolder);
+ UUID actualKey = UuidUtility.uuidFromArrowBuf(keyHolder.buffer, keyHolder.start);
+ assertEquals(key1, actualKey);
+
+ FieldReader valueReader = mapReader.value();
+ assertTrue(valueReader.isSet());
+ List> listValue = (List>) valueReader.readObject();
+ assertEquals(2, listValue.size());
+
+ // Verify first list item - readObject() returns UUID objects for extension types
+ UUID actualValue1a = (UUID) listValue.get(0);
+ assertEquals(value1a, actualValue1a);
+
+ // Verify second list item
+ UUID actualValue1b = (UUID) listValue.get(1);
+ assertEquals(value1b, actualValue1b);
+
+ // Read second entry
+ mapReader.next();
+ keyReader = mapReader.key();
+ keyReader.read(keyHolder);
+ actualKey = UuidUtility.uuidFromArrowBuf(keyHolder.buffer, keyHolder.start);
+ assertEquals(key2, actualKey);
+
+ valueReader = mapReader.value();
+ assertTrue(valueReader.isSet());
+ listValue = (List>) valueReader.readObject();
+ assertEquals(3, listValue.size());
+
+ // Verify first list item - readObject() returns UUID objects for extension types
+ UUID actualValue2a = (UUID) listValue.get(0);
+ assertEquals(value2a, actualValue2a);
+
+ // Verify second list item
+ UUID actualValue2b = (UUID) listValue.get(1);
+ assertEquals(value2b, actualValue2b);
+
+ // Verify third list item
+ UUID actualValue2c = (UUID) listValue.get(2);
+ assertEquals(value2c, actualValue2c);
+ }
+ }
}
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestRunEndEncodedVector.java b/vector/src/test/java/org/apache/arrow/vector/TestRunEndEncodedVector.java
index adf51c0730..9fa153e928 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestRunEndEncodedVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestRunEndEncodedVector.java
@@ -148,12 +148,18 @@ public void testRangeCompare() {
assertTrue(
constantVector.accept(
new RangeEqualsVisitor(constantVector, constantVector), new Range(1, 2, 13)));
- assertFalse(
- constantVector.accept(
- new RangeEqualsVisitor(constantVector, constantVector), new Range(1, 10, 10)));
- assertFalse(
- constantVector.accept(
- new RangeEqualsVisitor(constantVector, constantVector), new Range(10, 1, 10)));
+
+ // throws exception if the range end is out the bound of the vector
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ constantVector.accept(
+ new RangeEqualsVisitor(constantVector, constantVector), new Range(1, 10, 10)));
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ constantVector.accept(
+ new RangeEqualsVisitor(constantVector, constantVector), new Range(10, 1, 10)));
// Create REE vector representing: [1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5].
RunEndEncodedVector reeVector =
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestStructVector.java b/vector/src/test/java/org/apache/arrow/vector/TestStructVector.java
index d40af9ae89..8c8a45f588 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestStructVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestStructVector.java
@@ -35,6 +35,7 @@
import org.apache.arrow.vector.complex.impl.NullableStructWriter;
import org.apache.arrow.vector.complex.writer.Float8Writer;
import org.apache.arrow.vector.complex.writer.IntWriter;
+import org.apache.arrow.vector.extension.UuidType;
import org.apache.arrow.vector.holders.ComplexHolder;
import org.apache.arrow.vector.types.Types;
import org.apache.arrow.vector.types.Types.MinorType;
@@ -42,7 +43,6 @@
import org.apache.arrow.vector.types.pojo.ArrowType.Struct;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.FieldType;
-import org.apache.arrow.vector.types.pojo.UuidType;
import org.apache.arrow.vector.util.TransferPair;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -160,17 +160,23 @@ public void testGetPrimitiveVectors() {
UnionVector unionVector = vector.addOrGetUnion("union");
unionVector.addVector(new BigIntVector("bigInt", allocator));
unionVector.addVector(new SmallIntVector("smallInt", allocator));
+ unionVector.addVector(new UuidVector("uuid", allocator));
// add varchar vector
vector.addOrGet(
"varchar", FieldType.nullable(MinorType.VARCHAR.getType()), VarCharVector.class);
+ // add extension vector
+ vector.addOrGet("extension", FieldType.nullable(UuidType.INSTANCE), UuidVector.class);
+
List primitiveVectors = vector.getPrimitiveVectors();
- assertEquals(4, primitiveVectors.size());
+ assertEquals(6, primitiveVectors.size());
assertEquals(MinorType.INT, primitiveVectors.get(0).getMinorType());
assertEquals(MinorType.BIGINT, primitiveVectors.get(1).getMinorType());
assertEquals(MinorType.SMALLINT, primitiveVectors.get(2).getMinorType());
- assertEquals(MinorType.VARCHAR, primitiveVectors.get(3).getMinorType());
+ assertEquals(MinorType.EXTENSIONTYPE, primitiveVectors.get(3).getMinorType());
+ assertEquals(MinorType.VARCHAR, primitiveVectors.get(4).getMinorType());
+ assertEquals(MinorType.EXTENSIONTYPE, primitiveVectors.get(5).getMinorType());
}
}
@@ -341,7 +347,7 @@ public void testGetTransferPairWithFieldAndCallBack() {
@Test
public void testStructVectorWithExtensionTypes() {
- UuidType uuidType = new UuidType();
+ UuidType uuidType = UuidType.INSTANCE;
Field uuidField = new Field("struct_child", FieldType.nullable(uuidType), null);
Field structField =
new Field("struct", FieldType.nullable(new ArrowType.Struct()), List.of(uuidField));
@@ -353,7 +359,7 @@ public void testStructVectorWithExtensionTypes() {
@Test
public void testStructVectorTransferPairWithExtensionType() {
- UuidType uuidType = new UuidType();
+ UuidType uuidType = UuidType.INSTANCE;
Field uuidField = new Field("uuid_child", FieldType.nullable(uuidType), null);
Field structField =
new Field("struct", FieldType.nullable(new ArrowType.Struct()), List.of(uuidField));
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestUtils.java b/vector/src/test/java/org/apache/arrow/vector/TestUtils.java
index 82295f8037..d91b2004c0 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestUtils.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestUtils.java
@@ -18,8 +18,10 @@
import java.util.Random;
import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.vector.holders.FixedSizeBinaryHolder;
import org.apache.arrow.vector.types.Types.MinorType;
import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.ExtensionTypeRegistry;
import org.apache.arrow.vector.types.pojo.FieldType;
public class TestUtils {
@@ -62,4 +64,26 @@ public static String generateRandomString(int length) {
}
return sb.toString();
}
+
+ /*
+ * Ensure the extension type is registered, as there might other tests trying to unregister the
+ * type. ex.: TestExtensionType#readUnderlyingType
+ */
+ public static void ensureRegistered(ArrowType.ExtensionType type) {
+ if (ExtensionTypeRegistry.lookup(type.extensionName()) == null) {
+ ExtensionTypeRegistry.register(type);
+ }
+ }
+
+ public static FixedSizeBinaryHolder fixedSizeBinaryHolder(
+ BufferAllocator allocator, byte[] array) {
+ FixedSizeBinaryHolder holder = new FixedSizeBinaryHolder();
+ holder.byteWidth = array.length;
+ holder.buffer = allocator.buffer(array.length);
+ for (int i = 0; i < array.length; i++) {
+ holder.buffer.setByte(i, array[i]);
+ }
+
+ return holder;
+ }
}
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestUuidType.java b/vector/src/test/java/org/apache/arrow/vector/TestUuidType.java
new file mode 100644
index 0000000000..99045d1cba
--- /dev/null
+++ b/vector/src/test/java/org/apache/arrow/vector/TestUuidType.java
@@ -0,0 +1,276 @@
+/*
+ * 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.vector;
+
+import static org.apache.arrow.vector.TestUtils.ensureRegistered;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.UUID;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.vector.dictionary.DictionaryProvider;
+import org.apache.arrow.vector.extension.UuidType;
+import org.apache.arrow.vector.ipc.ArrowStreamReader;
+import org.apache.arrow.vector.ipc.ArrowStreamWriter;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.Field;
+import org.apache.arrow.vector.types.pojo.FieldType;
+import org.apache.arrow.vector.types.pojo.Schema;
+import org.apache.arrow.vector.util.UuidUtility;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestUuidType {
+ BufferAllocator allocator;
+
+ @BeforeEach
+ void beforeEach() {
+ allocator = new RootAllocator();
+ }
+
+ @AfterEach
+ void afterEach() {
+ allocator.close();
+ }
+
+ @Test
+ void testConstants() {
+ assertEquals("arrow.uuid", UuidType.EXTENSION_NAME);
+ assertNotNull(UuidType.INSTANCE);
+ assertNotNull(UuidType.STORAGE_TYPE);
+ assertInstanceOf(ArrowType.FixedSizeBinary.class, UuidType.STORAGE_TYPE);
+ assertEquals(
+ UuidType.UUID_BYTE_WIDTH,
+ ((ArrowType.FixedSizeBinary) UuidType.STORAGE_TYPE).getByteWidth());
+ }
+
+ @Test
+ void testStorageType() {
+ UuidType type = UuidType.INSTANCE;
+ assertEquals(UuidType.STORAGE_TYPE, type.storageType());
+ assertInstanceOf(ArrowType.FixedSizeBinary.class, type.storageType());
+ }
+
+ @Test
+ void testExtensionName() {
+ UuidType type = UuidType.INSTANCE;
+ assertEquals("arrow.uuid", type.extensionName());
+ }
+
+ @Test
+ void testExtensionEquals() {
+ UuidType type1 = UuidType.INSTANCE;
+ UuidType type2 = UuidType.INSTANCE;
+ UuidType type3 = UuidType.INSTANCE;
+
+ assertTrue(type1.extensionEquals(type2));
+ assertTrue(type1.extensionEquals(type3));
+ assertTrue(type2.extensionEquals(type3));
+ }
+
+ @Test
+ void testIsComplex() {
+ UuidType type = UuidType.INSTANCE;
+ assertFalse(type.isComplex());
+ }
+
+ @Test
+ void testSerialize() {
+ UuidType type = UuidType.INSTANCE;
+ String serialized = type.serialize();
+ assertEquals("", serialized);
+ }
+
+ @Test
+ void testDeserializeValid() {
+ UuidType type = UuidType.INSTANCE;
+ ArrowType storageType = new ArrowType.FixedSizeBinary(UuidType.UUID_BYTE_WIDTH);
+
+ ArrowType deserialized = assertDoesNotThrow(() -> type.deserialize(storageType, ""));
+ assertInstanceOf(UuidType.class, deserialized);
+ assertEquals(UuidType.INSTANCE, deserialized);
+ }
+
+ @Test
+ void testDeserializeInvalidStorageType() {
+ UuidType type = UuidType.INSTANCE;
+ ArrowType wrongStorageType = new ArrowType.FixedSizeBinary(32);
+
+ assertThrows(UnsupportedOperationException.class, () -> type.deserialize(wrongStorageType, ""));
+ }
+
+ @Test
+ void testGetNewVector() {
+ UuidType type = UuidType.INSTANCE;
+ try (FieldVector vector =
+ type.getNewVector("uuid_field", FieldType.nullable(type), allocator)) {
+ assertInstanceOf(UuidVector.class, vector);
+ assertEquals("uuid_field", vector.getField().getName());
+ assertEquals(type, vector.getField().getType());
+ }
+ }
+
+ @Test
+ void testVectorOperations() {
+ UuidType type = UuidType.INSTANCE;
+ try (FieldVector vector =
+ type.getNewVector("uuid_field", FieldType.nullable(type), allocator)) {
+ UuidVector uuidVector = (UuidVector) vector;
+
+ UUID uuid1 = UUID.randomUUID();
+ UUID uuid2 = UUID.randomUUID();
+
+ uuidVector.setSafe(0, uuid1);
+ uuidVector.setSafe(1, uuid2);
+ uuidVector.setNull(2);
+ uuidVector.setValueCount(3);
+
+ assertEquals(uuid1, uuidVector.getObject(0));
+ assertEquals(uuid2, uuidVector.getObject(1));
+ assertNull(uuidVector.getObject(2));
+ assertFalse(uuidVector.isNull(0));
+ assertFalse(uuidVector.isNull(1));
+ assertTrue(uuidVector.isNull(2));
+ }
+ }
+
+ @Test
+ void testIpcRoundTrip() {
+ UuidType type = UuidType.INSTANCE;
+ ensureRegistered(type);
+
+ Schema schema = new Schema(Collections.singletonList(Field.nullable("uuid", type)));
+ byte[] serialized = schema.serializeAsMessage();
+ Schema deserialized = Schema.deserializeMessage(ByteBuffer.wrap(serialized));
+ assertEquals(schema, deserialized);
+ }
+
+ @Test
+ void testVectorIpcRoundTrip() throws IOException {
+ UuidType type = UuidType.INSTANCE;
+ ensureRegistered(type);
+
+ UUID uuid1 = UUID.randomUUID();
+ UUID uuid2 = UUID.randomUUID();
+
+ try (FieldVector vector = type.getNewVector("field", FieldType.nullable(type), allocator)) {
+ UuidVector uuidVector = (UuidVector) vector;
+ uuidVector.setSafe(0, uuid1);
+ uuidVector.setNull(1);
+ uuidVector.setSafe(2, uuid2);
+ uuidVector.setValueCount(3);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (VectorSchemaRoot root = new VectorSchemaRoot(Collections.singletonList(uuidVector));
+ ArrowStreamWriter writer =
+ new ArrowStreamWriter(root, new DictionaryProvider.MapDictionaryProvider(), baos)) {
+ writer.start();
+ writer.writeBatch();
+ }
+
+ try (ArrowStreamReader reader =
+ new ArrowStreamReader(new ByteArrayInputStream(baos.toByteArray()), allocator)) {
+ assertTrue(reader.loadNextBatch());
+ VectorSchemaRoot root = reader.getVectorSchemaRoot();
+ assertEquals(3, root.getRowCount());
+ assertEquals(
+ new Schema(Collections.singletonList(uuidVector.getField())), root.getSchema());
+
+ UuidVector actual = assertInstanceOf(UuidVector.class, root.getVector("field"));
+ assertFalse(actual.isNull(0));
+ assertTrue(actual.isNull(1));
+ assertFalse(actual.isNull(2));
+ assertEquals(uuid1, actual.getObject(0));
+ assertNull(actual.getObject(1));
+ assertEquals(uuid2, actual.getObject(2));
+ }
+ }
+ }
+
+ @Test
+ void testVectorByteArrayOperations() {
+ UuidType type = UuidType.INSTANCE;
+ try (FieldVector vector =
+ type.getNewVector("uuid_field", FieldType.nullable(type), allocator)) {
+ UuidVector uuidVector = (UuidVector) vector;
+
+ UUID uuid = UUID.randomUUID();
+ byte[] uuidBytes = UuidUtility.getBytesFromUUID(uuid);
+
+ uuidVector.setSafe(0, uuidBytes);
+ uuidVector.setValueCount(1);
+
+ assertEquals(uuid, uuidVector.getObject(0));
+
+ // Verify the bytes match
+ byte[] actualBytes = new byte[UuidType.UUID_BYTE_WIDTH];
+ int offset = uuidVector.getStartOffset(0);
+ uuidVector.getDataBuffer().getBytes(offset, actualBytes);
+ assertArrayEquals(uuidBytes, actualBytes);
+ }
+ }
+
+ @Test
+ void testGetNewVectorWithCustomFieldType() {
+ UuidType type = UuidType.INSTANCE;
+ FieldType fieldType = new FieldType(false, type, null);
+
+ try (FieldVector vector = type.getNewVector("non_nullable_uuid", fieldType, allocator)) {
+ assertInstanceOf(UuidVector.class, vector);
+ assertEquals("non_nullable_uuid", vector.getField().getName());
+ assertFalse(vector.getField().isNullable());
+ }
+ }
+
+ @Test
+ void testSingleton() {
+ UuidType type1 = UuidType.INSTANCE;
+ UuidType type2 = UuidType.INSTANCE;
+
+ // Same instance
+ assertSame(type1, type2);
+ assertTrue(type1.extensionEquals(type2));
+ }
+
+ @Test
+ void testUnderlyingVector() {
+ UuidType type = UuidType.INSTANCE;
+ try (FieldVector vector =
+ type.getNewVector("uuid_field", FieldType.nullable(type), allocator)) {
+ UuidVector uuidVector = (UuidVector) vector;
+ FixedSizeBinaryVector underlying = uuidVector.getUnderlyingVector();
+
+ assertInstanceOf(FixedSizeBinaryVector.class, underlying);
+ assertEquals(UuidType.UUID_BYTE_WIDTH, underlying.getByteWidth());
+ }
+ }
+}
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestUuidVector.java b/vector/src/test/java/org/apache/arrow/vector/TestUuidVector.java
new file mode 100644
index 0000000000..b5dd12d89c
--- /dev/null
+++ b/vector/src/test/java/org/apache/arrow/vector/TestUuidVector.java
@@ -0,0 +1,726 @@
+/*
+ * 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.vector;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+import org.apache.arrow.memory.ArrowBuf;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.vector.complex.impl.NullableUuidHolderReaderImpl;
+import org.apache.arrow.vector.complex.impl.UuidReaderImpl;
+import org.apache.arrow.vector.complex.impl.UuidWriterImpl;
+import org.apache.arrow.vector.extension.UuidType;
+import org.apache.arrow.vector.holders.ExtensionHolder;
+import org.apache.arrow.vector.holders.NullableUuidHolder;
+import org.apache.arrow.vector.holders.UuidHolder;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.util.UuidUtility;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** Tests for UuidVector, UuidWriterImpl, and UuidReaderImpl. */
+class TestUuidVector {
+
+ private BufferAllocator allocator;
+
+ @BeforeEach
+ void beforeEach() {
+ allocator = new RootAllocator();
+ }
+
+ @AfterEach
+ void afterEach() {
+ allocator.close();
+ }
+
+ // ========== Writer Tests ==========
+
+ @Test
+ void testWriteToExtensionVector() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator);
+ UuidWriterImpl writer = new UuidWriterImpl(vector)) {
+ UUID uuid = UUID.randomUUID();
+ ByteBuffer bb = ByteBuffer.allocate(UuidType.UUID_BYTE_WIDTH);
+ bb.putLong(uuid.getMostSignificantBits());
+ bb.putLong(uuid.getLeastSignificantBits());
+
+ // Allocate ArrowBuf for the holder
+ try (ArrowBuf buf = allocator.buffer(UuidType.UUID_BYTE_WIDTH)) {
+ buf.setBytes(0, bb.array());
+
+ UuidHolder holder = new UuidHolder();
+ holder.buffer = buf;
+
+ writer.write(holder);
+ UUID result = vector.getObject(0);
+ assertEquals(uuid, result);
+ }
+ }
+ }
+
+ @Test
+ void testWriteExtensionWithUUID() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator);
+ UuidWriterImpl writer = new UuidWriterImpl(vector)) {
+ UUID uuid = UUID.randomUUID();
+ writer.setPosition(0);
+ writer.writeExtension(uuid);
+
+ UUID result = vector.getObject(0);
+ assertEquals(uuid, result);
+ assertEquals(1, vector.getValueCount());
+ }
+ }
+
+ @Test
+ void testWriteExtensionWithByteArray() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator);
+ UuidWriterImpl writer = new UuidWriterImpl(vector)) {
+ UUID uuid = UUID.randomUUID();
+ byte[] uuidBytes = UuidUtility.getBytesFromUUID(uuid);
+
+ writer.setPosition(0);
+ writer.writeExtension(uuidBytes);
+
+ UUID result = vector.getObject(0);
+ assertEquals(uuid, result);
+ assertEquals(1, vector.getValueCount());
+ }
+ }
+
+ @Test
+ void testWriteExtensionWithArrowBuf() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator);
+ UuidWriterImpl writer = new UuidWriterImpl(vector);
+ ArrowBuf buf = allocator.buffer(UuidType.UUID_BYTE_WIDTH)) {
+ UUID uuid = UUID.randomUUID();
+ byte[] uuidBytes = UuidUtility.getBytesFromUUID(uuid);
+ buf.setBytes(0, uuidBytes);
+
+ writer.setPosition(0);
+ writer.writeExtension(buf);
+
+ UUID result = vector.getObject(0);
+ assertEquals(uuid, result);
+ assertEquals(1, vector.getValueCount());
+ }
+ }
+
+ @Test
+ void testWriteExtensionWithUnsupportedType() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator);
+ UuidWriterImpl writer = new UuidWriterImpl(vector)) {
+ writer.setPosition(0);
+
+ IllegalArgumentException exception =
+ assertThrows(IllegalArgumentException.class, () -> writer.writeExtension("invalid-type"));
+
+ assertTrue(
+ exception.getMessage().contains("Unsupported value type for UUID: java.lang.String"));
+ }
+ }
+
+ @Test
+ void testWriteExtensionMultipleValues() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator);
+ UuidWriterImpl writer = new UuidWriterImpl(vector)) {
+ UUID uuid1 = UUID.randomUUID();
+ UUID uuid2 = UUID.randomUUID();
+ UUID uuid3 = UUID.randomUUID();
+
+ writer.setPosition(0);
+ writer.writeExtension(uuid1);
+ writer.setPosition(1);
+ writer.writeExtension(uuid2);
+ writer.setPosition(2);
+ writer.writeExtension(uuid3);
+
+ assertEquals(uuid1, vector.getObject(0));
+ assertEquals(uuid2, vector.getObject(1));
+ assertEquals(uuid3, vector.getObject(2));
+ assertEquals(3, vector.getValueCount());
+ }
+ }
+
+ @Test
+ void testWriteWithUuidHolder() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator);
+ UuidWriterImpl writer = new UuidWriterImpl(vector);
+ ArrowBuf buf = allocator.buffer(UuidType.UUID_BYTE_WIDTH)) {
+ UUID uuid = UUID.randomUUID();
+ byte[] uuidBytes = UuidUtility.getBytesFromUUID(uuid);
+ buf.setBytes(0, uuidBytes);
+
+ UuidHolder holder = new UuidHolder();
+ holder.buffer = buf;
+ holder.isSet = 1;
+
+ writer.setPosition(0);
+ writer.write(holder);
+
+ UUID result = vector.getObject(0);
+ assertEquals(uuid, result);
+ assertEquals(1, vector.getValueCount());
+ }
+ }
+
+ @Test
+ void testWriteWithNullableUuidHolder() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator);
+ UuidWriterImpl writer = new UuidWriterImpl(vector);
+ ArrowBuf buf = allocator.buffer(UuidType.UUID_BYTE_WIDTH)) {
+ UUID uuid = UUID.randomUUID();
+ byte[] uuidBytes = UuidUtility.getBytesFromUUID(uuid);
+ buf.setBytes(0, uuidBytes);
+
+ NullableUuidHolder holder = new NullableUuidHolder();
+ holder.buffer = buf;
+ holder.isSet = 1;
+
+ writer.setPosition(0);
+ writer.write(holder);
+
+ UUID result = vector.getObject(0);
+ assertEquals(uuid, result);
+ assertEquals(1, vector.getValueCount());
+ }
+ }
+
+ @Test
+ void testWriteWithNullableUuidHolderNull() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator);
+ UuidWriterImpl writer = new UuidWriterImpl(vector)) {
+ NullableUuidHolder holder = new NullableUuidHolder();
+ holder.isSet = 0;
+
+ writer.setPosition(0);
+ writer.write(holder);
+
+ assertTrue(vector.isNull(0));
+ assertEquals(1, vector.getValueCount());
+ }
+ }
+
+ // ========== Reader Tests ==========
+
+ @Test
+ void testReaderCopyAsValueExtensionVector() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator);
+ UuidVector vectorForRead = new UuidVector("test2", allocator);
+ UuidWriterImpl writer = new UuidWriterImpl(vector)) {
+ UUID uuid = UUID.randomUUID();
+ vectorForRead.setValueCount(1);
+ vectorForRead.set(0, uuid);
+ UuidReaderImpl reader = (UuidReaderImpl) vectorForRead.getReader();
+ reader.copyAsValue(writer);
+ UuidReaderImpl reader2 = (UuidReaderImpl) vector.getReader();
+ NullableUuidHolder holder = new NullableUuidHolder();
+ reader2.read(0, holder);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(uuid, actualUuid);
+ }
+ }
+
+ @Test
+ void testReaderReadWithUuidHolder() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UUID uuid = UUID.randomUUID();
+ vector.setSafe(0, uuid);
+ vector.setValueCount(1);
+
+ UuidReaderImpl reader = (UuidReaderImpl) vector.getReader();
+ reader.setPosition(0);
+
+ NullableUuidHolder holder = new NullableUuidHolder();
+ reader.read(holder);
+
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(uuid, actualUuid);
+ assertEquals(1, holder.isSet);
+ }
+ }
+
+ @Test
+ void testReaderReadWithNullableUuidHolder() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UUID uuid = UUID.randomUUID();
+ vector.setSafe(0, uuid);
+ vector.setValueCount(1);
+
+ UuidReaderImpl reader = (UuidReaderImpl) vector.getReader();
+ reader.setPosition(0);
+
+ NullableUuidHolder holder = new NullableUuidHolder();
+ reader.read(holder);
+
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(uuid, actualUuid);
+ assertEquals(1, holder.isSet);
+ }
+ }
+
+ @Test
+ void testReaderReadWithNullableUuidHolderNull() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ vector.setNull(0);
+ vector.setValueCount(1);
+
+ UuidReaderImpl reader = (UuidReaderImpl) vector.getReader();
+ reader.setPosition(0);
+
+ NullableUuidHolder holder = new NullableUuidHolder();
+ reader.read(holder);
+
+ assertEquals(0, holder.isSet);
+ }
+ }
+
+ @Test
+ void testReaderReadWithArrayIndexUuidHolder() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UUID uuid1 = UUID.randomUUID();
+ UUID uuid2 = UUID.randomUUID();
+ UUID uuid3 = UUID.randomUUID();
+
+ vector.setSafe(0, uuid1);
+ vector.setSafe(1, uuid2);
+ vector.setSafe(2, uuid3);
+ vector.setValueCount(3);
+
+ UuidReaderImpl reader = (UuidReaderImpl) vector.getReader();
+
+ NullableUuidHolder holder = new NullableUuidHolder();
+ reader.read(1, holder);
+
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
+ assertEquals(uuid2, actualUuid);
+ assertEquals(1, holder.isSet);
+ }
+ }
+
+ @Test
+ void testReaderReadWithArrayIndexNullableUuidHolder() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UUID uuid1 = UUID.randomUUID();
+ UUID uuid2 = UUID.randomUUID();
+
+ vector.setSafe(0, uuid1);
+ vector.setNull(1);
+ vector.setSafe(2, uuid2);
+ vector.setValueCount(3);
+
+ UuidReaderImpl reader = (UuidReaderImpl) vector.getReader();
+
+ NullableUuidHolder holder1 = new NullableUuidHolder();
+ reader.read(0, holder1);
+ assertEquals(uuid1, UuidUtility.uuidFromArrowBuf(holder1.buffer, holder1.start));
+ assertEquals(1, holder1.isSet);
+
+ NullableUuidHolder holder2 = new NullableUuidHolder();
+ reader.read(1, holder2);
+ assertEquals(0, holder2.isSet);
+
+ NullableUuidHolder holder3 = new NullableUuidHolder();
+ reader.read(2, holder3);
+ assertEquals(uuid2, UuidUtility.uuidFromArrowBuf(holder3.buffer, holder3.start));
+ assertEquals(1, holder3.isSet);
+ }
+ }
+
+ @Test
+ void testReaderReadWithUnsupportedHolder() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UUID uuid = UUID.randomUUID();
+ vector.setSafe(0, uuid);
+ vector.setValueCount(1);
+
+ UuidReaderImpl reader = (UuidReaderImpl) vector.getReader();
+ reader.setPosition(0);
+
+ // Create a mock unsupported holder
+ ExtensionHolder unsupportedHolder =
+ new ExtensionHolder() {
+ @Override
+ public ArrowType type() {
+ return null;
+ }
+ };
+
+ IllegalArgumentException exception =
+ assertThrows(IllegalArgumentException.class, () -> reader.read(unsupportedHolder));
+
+ assertTrue(exception.getMessage().contains("Unsupported holder type for UuidReader"));
+ }
+ }
+
+ @Test
+ void testReaderIsSet() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UUID uuid = UUID.randomUUID();
+ vector.setSafe(0, uuid);
+ vector.setNull(1);
+ vector.setSafe(2, uuid);
+ vector.setValueCount(3);
+
+ UuidReaderImpl reader = (UuidReaderImpl) vector.getReader();
+
+ reader.setPosition(0);
+ assertTrue(reader.isSet());
+
+ reader.setPosition(1);
+ assertFalse(reader.isSet());
+
+ reader.setPosition(2);
+ assertTrue(reader.isSet());
+ }
+ }
+
+ @Test
+ void testReaderReadObject() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UUID uuid1 = UUID.randomUUID();
+ UUID uuid2 = UUID.randomUUID();
+
+ vector.setSafe(0, uuid1);
+ vector.setNull(1);
+ vector.setSafe(2, uuid2);
+ vector.setValueCount(3);
+
+ UuidReaderImpl reader = (UuidReaderImpl) vector.getReader();
+
+ reader.setPosition(0);
+ assertEquals(uuid1, reader.readObject());
+
+ reader.setPosition(1);
+ assertNull(reader.readObject());
+
+ reader.setPosition(2);
+ assertEquals(uuid2, reader.readObject());
+ }
+ }
+
+ @Test
+ void testReaderGetMinorType() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UuidReaderImpl reader = (UuidReaderImpl) vector.getReader();
+ assertEquals(vector.getMinorType(), reader.getMinorType());
+ }
+ }
+
+ @Test
+ void testReaderGetField() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UuidReaderImpl reader = (UuidReaderImpl) vector.getReader();
+ assertEquals(vector.getField(), reader.getField());
+ assertEquals("test", reader.getField().getName());
+ }
+ }
+
+ @Test
+ void testHolderStartOffsetWithMultipleValues() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UUID uuid1 = UUID.randomUUID();
+ UUID uuid2 = UUID.randomUUID();
+ UUID uuid3 = UUID.randomUUID();
+
+ vector.setSafe(0, uuid1);
+ vector.setSafe(1, uuid2);
+ vector.setSafe(2, uuid3);
+ vector.setValueCount(3);
+
+ // Test UuidHolder with different indices
+ NullableUuidHolder holder = new NullableUuidHolder();
+ vector.get(0, holder);
+ assertEquals(0, holder.start);
+ assertEquals(uuid1, UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start));
+
+ vector.get(1, holder);
+ assertEquals(16, holder.start); // UUID_BYTE_WIDTH = 16
+ assertEquals(uuid2, UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start));
+
+ vector.get(2, holder);
+ assertEquals(32, holder.start); // 2 * UUID_BYTE_WIDTH = 32
+ assertEquals(uuid3, UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start));
+ }
+ }
+
+ @Test
+ void testNullableHolderStartOffsetWithMultipleValues() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UUID uuid1 = UUID.randomUUID();
+ UUID uuid2 = UUID.randomUUID();
+
+ vector.setSafe(0, uuid1);
+ vector.setNull(1);
+ vector.setSafe(2, uuid2);
+ vector.setValueCount(3);
+
+ // Test NullableUuidHolder with different indices
+ NullableUuidHolder holder1 = new NullableUuidHolder();
+ vector.get(0, holder1);
+ assertEquals(0, holder1.start);
+ assertEquals(1, holder1.isSet);
+ assertEquals(uuid1, UuidUtility.uuidFromArrowBuf(holder1.buffer, holder1.start));
+
+ NullableUuidHolder holder2 = new NullableUuidHolder();
+ vector.get(1, holder2);
+ assertEquals(0, holder2.isSet);
+
+ NullableUuidHolder holder3 = new NullableUuidHolder();
+ vector.get(2, holder3);
+ assertEquals(32, holder3.start); // 2 * UUID_BYTE_WIDTH = 32
+ assertEquals(1, holder3.isSet);
+ assertEquals(uuid2, UuidUtility.uuidFromArrowBuf(holder3.buffer, holder3.start));
+
+ // Verify all holders share the same buffer
+ assertEquals(holder1.buffer, holder3.buffer);
+ }
+ }
+
+ @Test
+ void testSetFromHolderWithStartOffset() throws Exception {
+ try (UuidVector sourceVector = new UuidVector("source", allocator);
+ UuidVector targetVector = new UuidVector("target", allocator)) {
+ UUID uuid1 = UUID.randomUUID();
+ UUID uuid2 = UUID.randomUUID();
+
+ sourceVector.setSafe(0, uuid1);
+ sourceVector.setSafe(1, uuid2);
+ sourceVector.setValueCount(3);
+
+ // Get holder from index 1 (should have start = 16)
+ NullableUuidHolder holder = new NullableUuidHolder();
+ sourceVector.get(1, holder);
+ assertEquals(16, holder.start);
+
+ // Set target vector using holder with non-zero start offset
+ targetVector.setSafe(0, holder);
+ targetVector.setValueCount(1);
+
+ // Verify the value was copied correctly
+ assertEquals(uuid2, targetVector.getObject(0));
+ }
+ }
+
+ @Test
+ void testSetFromNullableHolderWithStartOffset() throws Exception {
+ try (UuidVector sourceVector = new UuidVector("source", allocator);
+ UuidVector targetVector = new UuidVector("target", allocator)) {
+ UUID uuid1 = UUID.randomUUID();
+ UUID uuid2 = UUID.randomUUID();
+
+ sourceVector.setSafe(0, uuid1);
+ sourceVector.setNull(1);
+ sourceVector.setSafe(2, uuid2);
+ sourceVector.setValueCount(3);
+
+ // Get holder from index 2 (should have start = 32)
+ NullableUuidHolder holder = new NullableUuidHolder();
+ sourceVector.get(2, holder);
+ assertEquals(32, holder.start);
+ assertEquals(1, holder.isSet);
+
+ // Set target vector using holder with non-zero start offset
+ targetVector.setSafe(0, holder);
+ targetVector.setValueCount(1);
+
+ // Verify the value was copied correctly
+ assertEquals(uuid2, targetVector.getObject(0));
+
+ // Test with null holder
+ NullableUuidHolder nullHolder = new NullableUuidHolder();
+ sourceVector.get(1, nullHolder);
+ assertEquals(0, nullHolder.isSet);
+
+ targetVector.setSafe(1, nullHolder);
+ targetVector.setValueCount(2);
+ assertTrue(targetVector.isNull(1));
+ }
+ }
+
+ @Test
+ void testGetStartOffset() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ vector.allocateNew(10);
+
+ // Test getStartOffset for various indices
+ assertEquals(0, vector.getStartOffset(0));
+ assertEquals(16, vector.getStartOffset(1));
+ assertEquals(32, vector.getStartOffset(2));
+ assertEquals(48, vector.getStartOffset(3));
+ assertEquals(160, vector.getStartOffset(10));
+ }
+ }
+
+ @Test
+ void testReaderWithStartOffsetMultipleReads() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UUID uuid1 = UUID.randomUUID();
+ UUID uuid2 = UUID.randomUUID();
+ UUID uuid3 = UUID.randomUUID();
+
+ vector.setSafe(0, uuid1);
+ vector.setSafe(1, uuid2);
+ vector.setSafe(2, uuid3);
+ vector.setValueCount(3);
+
+ UuidReaderImpl reader = (UuidReaderImpl) vector.getReader();
+ NullableUuidHolder holder = new NullableUuidHolder();
+
+ // Read from different positions and verify start offset
+ reader.read(0, holder);
+ assertEquals(0, holder.start);
+ assertEquals(uuid1, UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start));
+
+ reader.read(1, holder);
+ assertEquals(16, holder.start);
+ assertEquals(uuid2, UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start));
+
+ reader.read(2, holder);
+ assertEquals(32, holder.start);
+ assertEquals(uuid3, UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start));
+ }
+ }
+
+ @Test
+ void testWriterWithExtensionHolder() throws Exception {
+ try (UuidVector sourceVector = new UuidVector("source", allocator);
+ UuidVector targetVector = new UuidVector("target", allocator)) {
+ UUID uuid = UUID.randomUUID();
+ sourceVector.setSafe(0, uuid);
+ sourceVector.setValueCount(1);
+
+ // Get holder from source
+ NullableUuidHolder holder = new NullableUuidHolder();
+ sourceVector.get(0, holder);
+
+ // Write using UuidWriterImpl with ExtensionHolder
+ UuidWriterImpl writer = new UuidWriterImpl(targetVector);
+ writer.setPosition(0);
+ writer.writeExtension(holder);
+
+ assertEquals(uuid, targetVector.getObject(0));
+ }
+ }
+
+ @Test
+ void testNullableUuidHolderReaderImpl() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UUID uuid = UUID.randomUUID();
+ vector.setSafe(0, uuid);
+ vector.setValueCount(1);
+
+ // Get holder from vector
+ NullableUuidHolder sourceHolder = new NullableUuidHolder();
+ vector.get(0, sourceHolder);
+ assertEquals(1, sourceHolder.isSet);
+ assertEquals(0, sourceHolder.start);
+
+ // Create reader from holder
+ NullableUuidHolderReaderImpl reader = new NullableUuidHolderReaderImpl(sourceHolder);
+ assertTrue(reader.isSet());
+ assertEquals(uuid, reader.readObject());
+
+ // Read into another holder
+ NullableUuidHolder targetHolder = new NullableUuidHolder();
+ reader.read(targetHolder);
+ assertEquals(1, targetHolder.isSet);
+ assertEquals(0, targetHolder.start);
+ assertEquals(uuid, UuidUtility.uuidFromArrowBuf(targetHolder.buffer, targetHolder.start));
+ }
+ }
+
+ @Test
+ void testNullableUuidHolderReaderImplWithNull() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ vector.setNull(0);
+ vector.setValueCount(1);
+
+ // Get null holder from vector
+ NullableUuidHolder sourceHolder = new NullableUuidHolder();
+ vector.get(0, sourceHolder);
+ assertEquals(0, sourceHolder.isSet);
+
+ // Create reader from null holder
+ NullableUuidHolderReaderImpl reader = new NullableUuidHolderReaderImpl(sourceHolder);
+ assertFalse(reader.isSet());
+ assertNull(reader.readObject());
+
+ // Read into another holder
+ NullableUuidHolder targetHolder = new NullableUuidHolder();
+ reader.read(targetHolder);
+ assertEquals(0, targetHolder.isSet);
+ }
+ }
+
+ @Test
+ void testNullableUuidHolderReaderImplReadIntoUuidHolder() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UUID uuid = UUID.randomUUID();
+ vector.setSafe(0, uuid);
+ vector.setValueCount(1);
+
+ // Get holder from vector
+ NullableUuidHolder sourceHolder = new NullableUuidHolder();
+ vector.get(0, sourceHolder);
+
+ // Create reader from holder
+ NullableUuidHolderReaderImpl reader = new NullableUuidHolderReaderImpl(sourceHolder);
+
+ // Read into UuidHolder (non-nullable)
+ UuidHolder targetHolder = new UuidHolder();
+ reader.read(targetHolder);
+ assertEquals(0, targetHolder.start);
+ assertEquals(uuid, UuidUtility.uuidFromArrowBuf(targetHolder.buffer, targetHolder.start));
+ }
+ }
+
+ @Test
+ void testNullableUuidHolderReaderImplWithNonZeroStart() throws Exception {
+ try (UuidVector vector = new UuidVector("test", allocator)) {
+ UUID uuid1 = UUID.randomUUID();
+ UUID uuid2 = UUID.randomUUID();
+ vector.setSafe(0, uuid1);
+ vector.setSafe(1, uuid2);
+ vector.setValueCount(2);
+
+ // Get holder from index 1 (start = 16)
+ NullableUuidHolder sourceHolder = new NullableUuidHolder();
+ vector.get(1, sourceHolder);
+ assertEquals(1, sourceHolder.isSet);
+ assertEquals(16, sourceHolder.start);
+
+ // Create reader from holder
+ NullableUuidHolderReaderImpl reader = new NullableUuidHolderReaderImpl(sourceHolder);
+ assertEquals(uuid2, reader.readObject());
+
+ // Read into another holder and verify start is preserved
+ NullableUuidHolder targetHolder = new NullableUuidHolder();
+ reader.read(targetHolder);
+ assertEquals(16, targetHolder.start);
+ assertEquals(uuid2, UuidUtility.uuidFromArrowBuf(targetHolder.buffer, targetHolder.start));
+ }
+ }
+}
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestValueVector.java b/vector/src/test/java/org/apache/arrow/vector/TestValueVector.java
index daec331831..22c93b0cbe 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestValueVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestValueVector.java
@@ -16,6 +16,7 @@
*/
package org.apache.arrow.vector;
+import static org.apache.arrow.vector.BitVectorHelper.getValidityBufferSizeFromCount;
import static org.apache.arrow.vector.TestUtils.newVarBinaryVector;
import static org.apache.arrow.vector.TestUtils.newVarCharVector;
import static org.apache.arrow.vector.TestUtils.newVector;
@@ -56,6 +57,10 @@
import org.apache.arrow.vector.complex.impl.UnionListViewWriter;
import org.apache.arrow.vector.complex.impl.UnionListWriter;
import org.apache.arrow.vector.holders.NullableIntHolder;
+import org.apache.arrow.vector.holders.NullableTimeStampMicroTZHolder;
+import org.apache.arrow.vector.holders.NullableTimeStampMilliTZHolder;
+import org.apache.arrow.vector.holders.NullableTimeStampNanoTZHolder;
+import org.apache.arrow.vector.holders.NullableTimeStampSecTZHolder;
import org.apache.arrow.vector.holders.NullableUInt4Holder;
import org.apache.arrow.vector.holders.NullableVarBinaryHolder;
import org.apache.arrow.vector.holders.NullableVarCharHolder;
@@ -1233,7 +1238,7 @@ public void testSplitAndTransfer3() {
// the size needed for the validity buffer
final long validitySize =
DefaultRoundingPolicy.DEFAULT_ROUNDING_POLICY.getRoundedSize(
- BaseValueVector.getValidityBufferSizeFromCount(2));
+ getValidityBufferSizeFromCount(2));
assertEquals(allocatedMem + validitySize, allocator.getAllocatedMemory());
// The validity and offset buffers are sliced from a same buffer.See
// BaseFixedWidthVector#allocateBytes.
@@ -2464,7 +2469,7 @@ public void testDefaultAllocNewAll() {
assertTrue(intVector.getValueCapacity() >= defaultCapacity);
expectedSize =
(defaultCapacity * IntVector.TYPE_WIDTH)
- + BaseFixedWidthVector.getValidityBufferSizeFromCount(defaultCapacity);
+ + getValidityBufferSizeFromCount(defaultCapacity);
assertTrue(childAllocator.getAllocatedMemory() - beforeSize <= expectedSize * 1.05);
// verify that the wastage is within bounds for BigIntVector.
@@ -2473,7 +2478,7 @@ public void testDefaultAllocNewAll() {
assertTrue(bigIntVector.getValueCapacity() >= defaultCapacity);
expectedSize =
(defaultCapacity * bigIntVector.TYPE_WIDTH)
- + BaseFixedWidthVector.getValidityBufferSizeFromCount(defaultCapacity);
+ + getValidityBufferSizeFromCount(defaultCapacity);
assertTrue(childAllocator.getAllocatedMemory() - beforeSize <= expectedSize * 1.05);
// verify that the wastage is within bounds for DecimalVector.
@@ -2482,7 +2487,7 @@ public void testDefaultAllocNewAll() {
assertTrue(decimalVector.getValueCapacity() >= defaultCapacity);
expectedSize =
(defaultCapacity * decimalVector.TYPE_WIDTH)
- + BaseFixedWidthVector.getValidityBufferSizeFromCount(defaultCapacity);
+ + getValidityBufferSizeFromCount(defaultCapacity);
assertTrue(childAllocator.getAllocatedMemory() - beforeSize <= expectedSize * 1.05);
// verify that the wastage is within bounds for VarCharVector.
@@ -2492,7 +2497,7 @@ public void testDefaultAllocNewAll() {
assertTrue(varCharVector.getValueCapacity() >= defaultCapacity - 1);
expectedSize =
(defaultCapacity * VarCharVector.OFFSET_WIDTH)
- + BaseFixedWidthVector.getValidityBufferSizeFromCount(defaultCapacity)
+ + getValidityBufferSizeFromCount(defaultCapacity)
+ defaultCapacity * 8;
// wastage should be less than 5%.
assertTrue(childAllocator.getAllocatedMemory() - beforeSize <= expectedSize * 1.05);
@@ -2501,7 +2506,7 @@ public void testDefaultAllocNewAll() {
beforeSize = childAllocator.getAllocatedMemory();
bitVector.allocateNew();
assertTrue(bitVector.getValueCapacity() >= defaultCapacity);
- expectedSize = BaseFixedWidthVector.getValidityBufferSizeFromCount(defaultCapacity) * 2;
+ expectedSize = getValidityBufferSizeFromCount(defaultCapacity) * 2;
assertTrue(childAllocator.getAllocatedMemory() - beforeSize <= expectedSize * 1.05);
}
}
@@ -2566,6 +2571,195 @@ public void testSetNullableVarCharHolderSafe() {
}
}
+ @Test
+ public void testTimeStampTZVectorSetSafeUnset() {
+ // reproduction of https://github.com/apache/arrow/issues/45084
+ try (TimeStampMicroTZVector vector = new TimeStampMicroTZVector("vector", allocator, "UTC")) {
+ vector.allocateNew();
+ // Set a valid value
+ NullableTimeStampMicroTZHolder validHolder = new NullableTimeStampMicroTZHolder();
+ validHolder.isSet = 1;
+ validHolder.value = 1000L;
+ validHolder.timezone = "UTC";
+ vector.setSafe(0, validHolder);
+
+ assertEquals(1000L, vector.get(0));
+
+ // Unset the value using a holder with default (null) timezone
+ // The bug used to throw IllegalArgumentException because holder.timezone (null) !=
+ // vector.timezone ("UTC")
+ // The correct behaviour is to not throw an exception and to unset the value.
+ NullableTimeStampMicroTZHolder unsetHolder = new NullableTimeStampMicroTZHolder();
+ unsetHolder.isSet = 0;
+ vector.setSafe(0, unsetHolder);
+
+ assertNull(vector.getObject(0));
+ }
+ }
+
+ @Test
+ public void testTimeStampMilliTZVectorSetSafeUnset() {
+ // reproduction of https://github.com/apache/arrow/issues/45084
+ try (TimeStampMilliTZVector vector = new TimeStampMilliTZVector("vector", allocator, "UTC")) {
+ vector.allocateNew();
+
+ NullableTimeStampMilliTZHolder validHolder = new NullableTimeStampMilliTZHolder();
+ validHolder.isSet = 1;
+ validHolder.value = 1000L;
+ validHolder.timezone = "UTC";
+ vector.setSafe(0, validHolder);
+
+ assertEquals(1000L, vector.get(0));
+
+ NullableTimeStampMilliTZHolder unsetHolder = new NullableTimeStampMilliTZHolder();
+ unsetHolder.isSet = 0;
+ vector.setSafe(0, unsetHolder);
+
+ assertNull(vector.getObject(0));
+ }
+ }
+
+ @Test
+ public void testTimeStampNanoTZVectorSetSafeUnset() {
+ // reproduction of https://github.com/apache/arrow/issues/45084
+ try (TimeStampNanoTZVector vector = new TimeStampNanoTZVector("vector", allocator, "UTC")) {
+ vector.allocateNew();
+
+ NullableTimeStampNanoTZHolder validHolder = new NullableTimeStampNanoTZHolder();
+ validHolder.isSet = 1;
+ validHolder.value = 1000L;
+ validHolder.timezone = "UTC";
+ vector.setSafe(0, validHolder);
+
+ assertEquals(1000L, vector.get(0));
+
+ NullableTimeStampNanoTZHolder unsetHolder = new NullableTimeStampNanoTZHolder();
+ unsetHolder.isSet = 0;
+ vector.setSafe(0, unsetHolder);
+
+ assertNull(vector.getObject(0));
+ }
+ }
+
+ @Test
+ public void testTimeStampSecTZVectorSetSafeUnset() {
+ // reproduction of https://github.com/apache/arrow/issues/45084
+ try (TimeStampSecTZVector vector = new TimeStampSecTZVector("vector", allocator, "UTC")) {
+ vector.allocateNew();
+
+ NullableTimeStampSecTZHolder validHolder = new NullableTimeStampSecTZHolder();
+ validHolder.isSet = 1;
+ validHolder.value = 1000L;
+ validHolder.timezone = "UTC";
+ vector.setSafe(0, validHolder);
+
+ assertEquals(1000L, vector.get(0));
+
+ NullableTimeStampSecTZHolder unsetHolder = new NullableTimeStampSecTZHolder();
+ unsetHolder.isSet = 0;
+ vector.setSafe(0, unsetHolder);
+
+ assertNull(vector.getObject(0));
+ }
+ }
+
+ @Test
+ public void testTimeStampMicroTZVectorSetSafeUnsetExplicitTimezone() {
+ // Test to ensure fix added for https://github.com/apache/arrow/issues/45084 does not break
+ // workaround.
+ try (TimeStampMicroTZVector vector = new TimeStampMicroTZVector("vector", allocator, "UTC")) {
+ vector.allocateNew();
+
+ NullableTimeStampMicroTZHolder validHolder = new NullableTimeStampMicroTZHolder();
+ validHolder.isSet = 1;
+ validHolder.value = 1000L;
+ validHolder.timezone = "UTC";
+ vector.setSafe(0, validHolder);
+
+ assertEquals(1000L, vector.get(0));
+
+ NullableTimeStampMicroTZHolder unsetHolder = new NullableTimeStampMicroTZHolder();
+ unsetHolder.isSet = 0;
+ unsetHolder.timezone = "UTC";
+
+ vector.setSafe(0, unsetHolder);
+
+ assertNull(vector.getObject(0));
+ }
+ }
+
+ @Test
+ public void testTimeStampMilliTZVectorSetSafeUnsetExplicitTimezone() {
+ // Test to ensure fix added for https://github.com/apache/arrow/issues/45084 does not break
+ // workaround.
+ try (TimeStampMilliTZVector vector = new TimeStampMilliTZVector("vector", allocator, "UTC")) {
+ vector.allocateNew();
+
+ NullableTimeStampMilliTZHolder validHolder = new NullableTimeStampMilliTZHolder();
+ validHolder.isSet = 1;
+ validHolder.value = 1000L;
+ validHolder.timezone = "UTC";
+ vector.setSafe(0, validHolder);
+
+ assertEquals(1000L, vector.get(0));
+
+ NullableTimeStampMilliTZHolder unsetHolder = new NullableTimeStampMilliTZHolder();
+ unsetHolder.isSet = 0;
+ unsetHolder.timezone = "UTC";
+ vector.setSafe(0, unsetHolder);
+
+ assertNull(vector.getObject(0));
+ }
+ }
+
+ @Test
+ public void testTimeStampNanoTZVectorSetSafeUnsetExplicitTimezone() {
+ // Test to ensure fix added for https://github.com/apache/arrow/issues/45084 does not break
+ // workaround.
+ try (TimeStampNanoTZVector vector = new TimeStampNanoTZVector("vector", allocator, "UTC")) {
+ vector.allocateNew();
+
+ NullableTimeStampNanoTZHolder validHolder = new NullableTimeStampNanoTZHolder();
+ validHolder.isSet = 1;
+ validHolder.value = 1000L;
+ validHolder.timezone = "UTC";
+ vector.setSafe(0, validHolder);
+
+ assertEquals(1000L, vector.get(0));
+
+ NullableTimeStampNanoTZHolder unsetHolder = new NullableTimeStampNanoTZHolder();
+ unsetHolder.isSet = 0;
+ unsetHolder.timezone = "UTC";
+ vector.setSafe(0, unsetHolder);
+
+ assertNull(vector.getObject(0));
+ }
+ }
+
+ @Test
+ public void testTimeStampSecTZVectorSetSafeUnsetExplicitTimezone() {
+ // Test to ensure fix added for https://github.com/apache/arrow/issues/45084 does not break
+ // workaround.
+ try (TimeStampSecTZVector vector = new TimeStampSecTZVector("vector", allocator, "UTC")) {
+ vector.allocateNew();
+
+ NullableTimeStampSecTZHolder validHolder = new NullableTimeStampSecTZHolder();
+ validHolder.isSet = 1;
+ validHolder.value = 1000L;
+ validHolder.timezone = "UTC";
+ vector.setSafe(0, validHolder);
+
+ assertEquals(1000L, vector.get(0));
+
+ NullableTimeStampSecTZHolder unsetHolder = new NullableTimeStampSecTZHolder();
+ unsetHolder.isSet = 0;
+ unsetHolder.timezone = "UTC";
+ vector.setSafe(0, unsetHolder);
+
+ assertNull(vector.getObject(0));
+ }
+ }
+
@Test
public void testSetNullableVarBinaryHolder() {
try (VarBinaryVector vector = new VarBinaryVector("", allocator)) {
@@ -3746,4 +3940,42 @@ public void testVectorLoadUnloadOnNonVariadicVectors() {
}
}
}
+
+ @Test
+ public void testEmptyVarCharOffsetBuffer() {
+ // Validates that offset buffer has at least OFFSET_WIDTH bytes (for offset[0]=0)
+ // even when valueCount is 0, per Arrow specification.
+ try (VarCharVector vector = newVarCharVector("varchar", allocator)) {
+ vector.allocateNew();
+ vector.setValueCount(0);
+
+ List buffers = vector.getFieldBuffers();
+ // buffers: [validity, offset, data]
+ assertTrue(
+ buffers.get(1).readableBytes() >= BaseVariableWidthVector.OFFSET_WIDTH,
+ "Offset buffer should have at least "
+ + BaseVariableWidthVector.OFFSET_WIDTH
+ + " bytes for offset[0]");
+ assertEquals(0, vector.getOffsetBuffer().getInt(0));
+ }
+ }
+
+ @Test
+ public void testEmptyLargeVarCharOffsetBuffer() {
+ // Validates that offset buffer has at least OFFSET_WIDTH bytes (for offset[0]=0)
+ // even when valueCount is 0, per Arrow specification.
+ try (LargeVarCharVector vector = new LargeVarCharVector("largevarchar", allocator)) {
+ vector.allocateNew();
+ vector.setValueCount(0);
+
+ List buffers = vector.getFieldBuffers();
+ // buffers: [validity, offset, data]
+ assertTrue(
+ buffers.get(1).readableBytes() >= BaseLargeVariableWidthVector.OFFSET_WIDTH,
+ "Offset buffer should have at least "
+ + BaseLargeVariableWidthVector.OFFSET_WIDTH
+ + " bytes for offset[0]");
+ assertEquals(0, vector.getOffsetBuffer().getLong(0));
+ }
+ }
}
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestVariableWidthViewVector.java b/vector/src/test/java/org/apache/arrow/vector/TestVariableWidthViewVector.java
index 7a3a1bae63..baf5e672c8 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestVariableWidthViewVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestVariableWidthViewVector.java
@@ -16,6 +16,7 @@
*/
package org.apache.arrow.vector;
+import static org.apache.arrow.vector.BitVectorHelper.getValidityBufferSizeFromCount;
import static org.apache.arrow.vector.TestUtils.newVector;
import static org.apache.arrow.vector.TestUtils.newViewVarBinaryVector;
import static org.apache.arrow.vector.TestUtils.newViewVarCharVector;
@@ -60,6 +61,7 @@
import org.apache.arrow.vector.util.ReusableByteArray;
import org.apache.arrow.vector.util.Text;
import org.apache.arrow.vector.util.TransferPair;
+import org.apache.arrow.vector.validate.ValidateUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -2367,7 +2369,7 @@ private void testSplitAndTransferOnValiditySplitHelper(
// the allocation only consists in the size needed for the validity buffer
final long validitySize =
DefaultRoundingPolicy.DEFAULT_ROUNDING_POLICY.getRoundedSize(
- BaseValueVector.getValidityBufferSizeFromCount(2));
+ getValidityBufferSizeFromCount(2));
// we allocate view and data buffers for the target vector
assertTrue(allocatedMem + validitySize < allocator.getAllocatedMemory());
// The validity is sliced from the same buffer.See BaseFixedWidthViewVector#allocateBytes.
@@ -2444,7 +2446,7 @@ public void testSplitAndTransferWithLongStringsOnValiditySplit() {
final ViewVarBinaryVector sourceVector =
newViewVarBinaryVector(EMPTY_SCHEMA_PATH, allocator)) {
testSplitAndTransferOnValiditySplitHelper(
- targetVector, sourceVector, startIndex, length, data);
+ targetVector, sourceVector, startIndex, length, binaryData);
}
}
@@ -2851,4 +2853,18 @@ public void testVectorLoadUnloadOnMixedTypes() {
}
}
}
+
+ @Test
+ public void testValidate() {
+ try (final ViewVarCharVector vector = new ViewVarCharVector("v", allocator)) {
+ vector.validateFull();
+ setVector(vector, STR1, STR2, STR3);
+ vector.validateFull();
+
+ vector.getDataBuffer().capacity(0);
+ ValidateUtil.ValidateException e =
+ assertThrows(ValidateUtil.ValidateException.class, () -> vector.validate());
+ assertTrue(e.getMessage().contains("Not enough capacity for data buffer"));
+ }
+ }
}
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestVectorSchemaRoot.java b/vector/src/test/java/org/apache/arrow/vector/TestVectorSchemaRoot.java
index c121d94892..bd3113f8bc 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestVectorSchemaRoot.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestVectorSchemaRoot.java
@@ -171,6 +171,26 @@ public void testAddVector() {
}
}
+ @Test
+ public void testAddVectorAtEnd() {
+ try (final IntVector intVector1 = new IntVector("intVector1", allocator);
+ final IntVector intVector2 = new IntVector("intVector2", allocator);
+ final IntVector intVector3 = new IntVector("intVector3", allocator); ) {
+
+ VectorSchemaRoot original = new VectorSchemaRoot(Arrays.asList(intVector1, intVector2));
+ assertEquals(2, original.getFieldVectors().size());
+
+ VectorSchemaRoot newRecordBatch = original.addVector(2, intVector3);
+ assertEquals(3, newRecordBatch.getFieldVectors().size());
+ assertEquals(intVector1, newRecordBatch.getFieldVectors().get(0));
+ assertEquals(intVector2, newRecordBatch.getFieldVectors().get(1));
+ assertEquals(intVector3, newRecordBatch.getFieldVectors().get(2));
+
+ original.close();
+ newRecordBatch.close();
+ }
+ }
+
@Test
public void testRemoveVector() {
try (final IntVector intVector1 = new IntVector("intVector1", allocator);
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestVectorUnloadLoad.java b/vector/src/test/java/org/apache/arrow/vector/TestVectorUnloadLoad.java
index 6121fb67fe..782535fccc 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestVectorUnloadLoad.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestVectorUnloadLoad.java
@@ -17,6 +17,7 @@
package org.apache.arrow.vector;
import static java.util.Arrays.asList;
+import static org.apache.arrow.vector.BitVectorHelper.getValidityBufferSizeFromCount;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -215,7 +216,7 @@ public void testLoadValidityBuffer() throws IOException {
int count = 10;
ArrowBuf[] values = new ArrowBuf[4];
for (int i = 0; i < 4; i += 2) {
- ArrowBuf buf1 = allocator.buffer(BitVectorHelper.getValidityBufferSize(count));
+ ArrowBuf buf1 = allocator.buffer(getValidityBufferSizeFromCount(count));
ArrowBuf buf2 = allocator.buffer(count * 4); // integers
buf1.setZero(0, buf1.capacity());
buf2.setZero(0, buf2.capacity());
diff --git a/vector/src/test/java/org/apache/arrow/vector/UuidVector.java b/vector/src/test/java/org/apache/arrow/vector/UuidVector.java
deleted file mode 100644
index 5c90d45f60..0000000000
--- a/vector/src/test/java/org/apache/arrow/vector/UuidVector.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.vector;
-
-import java.nio.ByteBuffer;
-import java.util.UUID;
-import org.apache.arrow.memory.BufferAllocator;
-import org.apache.arrow.memory.util.hash.ArrowBufHasher;
-import org.apache.arrow.vector.types.pojo.Field;
-import org.apache.arrow.vector.types.pojo.FieldType;
-import org.apache.arrow.vector.types.pojo.UuidType;
-import org.apache.arrow.vector.util.TransferPair;
-
-public class UuidVector extends ExtensionTypeVector
- implements ValueIterableVector {
- private final Field field;
-
- public UuidVector(
- String name, BufferAllocator allocator, FixedSizeBinaryVector underlyingVector) {
- super(name, allocator, underlyingVector);
- this.field = new Field(name, FieldType.nullable(new UuidType()), null);
- }
-
- public UuidVector(String name, BufferAllocator allocator) {
- super(name, allocator, new FixedSizeBinaryVector(name, allocator, 16));
- this.field = new Field(name, FieldType.nullable(new UuidType()), null);
- }
-
- @Override
- public UUID getObject(int index) {
- final ByteBuffer bb = ByteBuffer.wrap(getUnderlyingVector().getObject(index));
- return new UUID(bb.getLong(), bb.getLong());
- }
-
- @Override
- public int hashCode(int index) {
- return hashCode(index, null);
- }
-
- @Override
- public int hashCode(int index, ArrowBufHasher hasher) {
- return getUnderlyingVector().hashCode(index, hasher);
- }
-
- public void set(int index, UUID uuid) {
- ByteBuffer bb = ByteBuffer.allocate(16);
- bb.putLong(uuid.getMostSignificantBits());
- bb.putLong(uuid.getLeastSignificantBits());
- getUnderlyingVector().set(index, bb.array());
- }
-
- @Override
- public void copyFromSafe(int fromIndex, int thisIndex, ValueVector from) {
- getUnderlyingVector()
- .copyFromSafe(fromIndex, thisIndex, ((UuidVector) from).getUnderlyingVector());
- }
-
- @Override
- public Field getField() {
- return field;
- }
-
- @Override
- public TransferPair makeTransferPair(ValueVector to) {
- return new TransferImpl((UuidVector) to);
- }
-
- public void setSafe(int index, byte[] value) {
- getUnderlyingVector().setIndexDefined(index);
- getUnderlyingVector().setSafe(index, value);
- }
-
- public class TransferImpl implements TransferPair {
- UuidVector to;
- ValueVector targetUnderlyingVector;
- TransferPair tp;
-
- public TransferImpl(UuidVector to) {
- this.to = to;
- targetUnderlyingVector = this.to.getUnderlyingVector();
- tp = getUnderlyingVector().makeTransferPair(targetUnderlyingVector);
- }
-
- public UuidVector getTo() {
- return this.to;
- }
-
- public void transfer() {
- tp.transfer();
- }
-
- public void splitAndTransfer(int startIndex, int length) {
- tp.splitAndTransfer(startIndex, length);
- }
-
- public void copyValueSafe(int fromIndex, int toIndex) {
- tp.copyValueSafe(fromIndex, toIndex);
- }
- }
-}
diff --git a/vector/src/test/java/org/apache/arrow/vector/compare/TestRangeEqualsVisitor.java b/vector/src/test/java/org/apache/arrow/vector/compare/TestRangeEqualsVisitor.java
index 08da786eb2..9624734356 100644
--- a/vector/src/test/java/org/apache/arrow/vector/compare/TestRangeEqualsVisitor.java
+++ b/vector/src/test/java/org/apache/arrow/vector/compare/TestRangeEqualsVisitor.java
@@ -22,6 +22,7 @@
import java.nio.charset.Charset;
import java.util.Arrays;
+import java.util.List;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.vector.BigIntVector;
@@ -39,6 +40,7 @@
import org.apache.arrow.vector.complex.LargeListViewVector;
import org.apache.arrow.vector.complex.ListVector;
import org.apache.arrow.vector.complex.ListViewVector;
+import org.apache.arrow.vector.complex.RunEndEncodedVector;
import org.apache.arrow.vector.complex.StructVector;
import org.apache.arrow.vector.complex.UnionVector;
import org.apache.arrow.vector.complex.impl.NullableStructWriter;
@@ -53,7 +55,9 @@
import org.apache.arrow.vector.holders.NullableUInt4Holder;
import org.apache.arrow.vector.types.FloatingPointPrecision;
import org.apache.arrow.vector.types.Types;
+import org.apache.arrow.vector.types.Types.MinorType;
import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.ArrowType.RunEndEncoded;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.FieldType;
import org.junit.jupiter.api.AfterEach;
@@ -1003,6 +1007,54 @@ public void testLargeListViewVectorApproxEquals() {
}
}
+ @Test
+ public void testRunEndEncodedFloat8ApproxEquals() {
+ try (final Float8Vector vector1 = new Float8Vector("float", allocator);
+ final Float8Vector vector2 = new Float8Vector("float", allocator);
+ final Float8Vector vector3 = new Float8Vector("float", allocator);
+ final IntVector reeVector = new IntVector("ree", allocator)) {
+
+ final float epsilon = 1.0E-6f;
+ setVector(vector1, 1.1, 2.2);
+ setVector(vector2, 1.1 + epsilon / 2, 2.2 + epsilon / 2);
+ setVector(vector3, 1.1 + epsilon * 2, 2.2 + epsilon * 2);
+ setVector(reeVector, 1, 3);
+
+ ArrowType type = MinorType.FLOAT8.getType();
+ final FieldType valueType = FieldType.notNullable(type);
+ final FieldType runEndType = FieldType.notNullable(MinorType.INT.getType());
+
+ final Field valueField = new Field("value", valueType, null);
+ final Field runEndField = new Field("ree", runEndType, null);
+
+ Field field =
+ new Field(
+ "ree_float",
+ FieldType.notNullable(RunEndEncoded.INSTANCE),
+ List.of(runEndField, valueField));
+
+ try (final RunEndEncodedVector encodedVector1 =
+ new RunEndEncodedVector(field, allocator, reeVector, vector1, null);
+ final RunEndEncodedVector encodedVector2 =
+ new RunEndEncodedVector(field, allocator, reeVector, vector2, null);
+ final RunEndEncodedVector encodedVector3 =
+ new RunEndEncodedVector(field, allocator, reeVector, vector3, null)) {
+
+ encodedVector1.setValueCount(3);
+ encodedVector2.setValueCount(3);
+ encodedVector3.setValueCount(3);
+
+ Range range = new Range(0, 0, encodedVector1.getValueCount());
+ assertTrue(
+ new ApproxEqualsVisitor(encodedVector1, encodedVector2, epsilon, epsilon)
+ .rangeEquals(range));
+ assertFalse(
+ new ApproxEqualsVisitor(encodedVector1, encodedVector3, epsilon, epsilon)
+ .rangeEquals(range));
+ }
+ }
+ }
+
private void writeStructVector(NullableStructWriter writer, int value1, long value2) {
writer.start();
writer.integer("f0").writeInt(value1);
diff --git a/vector/src/test/java/org/apache/arrow/vector/complex/impl/TestComplexCopier.java b/vector/src/test/java/org/apache/arrow/vector/complex/impl/TestComplexCopier.java
index 3bc02c6029..b2a8cf9ba4 100644
--- a/vector/src/test/java/org/apache/arrow/vector/complex/impl/TestComplexCopier.java
+++ b/vector/src/test/java/org/apache/arrow/vector/complex/impl/TestComplexCopier.java
@@ -20,6 +20,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigDecimal;
+import java.util.UUID;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.vector.DecimalVector;
@@ -30,8 +31,10 @@
import org.apache.arrow.vector.complex.StructVector;
import org.apache.arrow.vector.complex.reader.FieldReader;
import org.apache.arrow.vector.complex.writer.BaseWriter;
+import org.apache.arrow.vector.complex.writer.BaseWriter.ExtensionWriter;
import org.apache.arrow.vector.complex.writer.BaseWriter.StructWriter;
import org.apache.arrow.vector.complex.writer.FieldWriter;
+import org.apache.arrow.vector.extension.UuidType;
import org.apache.arrow.vector.holders.DecimalHolder;
import org.apache.arrow.vector.types.Types;
import org.apache.arrow.vector.types.pojo.ArrowType;
@@ -845,4 +848,110 @@ public void testCopyMapVectorWithMapValue() {
assertTrue(VectorEqualsVisitor.vectorEquals(from, to));
}
}
+
+ @Test
+ public void testCopyListVectorWithExtensionType() {
+ try (ListVector from = ListVector.empty("v", allocator);
+ ListVector to = ListVector.empty("v", allocator)) {
+
+ UnionListWriter listWriter = from.getWriter();
+ listWriter.allocate();
+
+ for (int i = 0; i < COUNT; i++) {
+ listWriter.setPosition(i);
+ listWriter.startList();
+ ExtensionWriter extensionWriter = listWriter.extension(UuidType.INSTANCE);
+ extensionWriter.writeExtension(UUID.randomUUID());
+ extensionWriter.writeExtension(UUID.randomUUID());
+ listWriter.endList();
+ }
+ from.setValueCount(COUNT);
+
+ // copy values
+ FieldReader in = from.getReader();
+ FieldWriter out = to.getWriter();
+ for (int i = 0; i < COUNT; i++) {
+ in.setPosition(i);
+ out.setPosition(i);
+ ComplexCopier.copy(in, out);
+ }
+
+ to.setValueCount(COUNT);
+
+ // validate equals
+ assertTrue(VectorEqualsVisitor.vectorEquals(from, to));
+ }
+ }
+
+ @Test
+ public void testCopyMapVectorWithExtensionType() {
+ try (final MapVector from = MapVector.empty("v", allocator, false);
+ final MapVector to = MapVector.empty("v", allocator, false)) {
+
+ from.allocateNew();
+
+ UnionMapWriter mapWriter = from.getWriter();
+ for (int i = 0; i < COUNT; i++) {
+ mapWriter.setPosition(i);
+ mapWriter.startMap();
+ mapWriter.startEntry();
+ ExtensionWriter extensionKeyWriter = mapWriter.key().extension(UuidType.INSTANCE);
+ extensionKeyWriter.writeExtension(UUID.randomUUID(), UuidType.INSTANCE);
+ ExtensionWriter extensionValueWriter = mapWriter.value().extension(UuidType.INSTANCE);
+ extensionValueWriter.writeExtension(UUID.randomUUID(), UuidType.INSTANCE);
+ mapWriter.endEntry();
+ mapWriter.endMap();
+ }
+
+ from.setValueCount(COUNT);
+
+ // copy values
+ FieldReader in = from.getReader();
+ FieldWriter out = to.getWriter();
+ for (int i = 0; i < COUNT; i++) {
+ in.setPosition(i);
+ out.setPosition(i);
+ ComplexCopier.copy(in, out);
+ }
+ to.setValueCount(COUNT);
+
+ // validate equals
+ assertTrue(VectorEqualsVisitor.vectorEquals(from, to));
+ }
+ }
+
+ @Test
+ public void testCopyStructVectorWithExtensionType() {
+ try (final StructVector from = StructVector.empty("v", allocator);
+ final StructVector to = StructVector.empty("v", allocator)) {
+
+ from.allocateNewSafe();
+
+ NullableStructWriter structWriter = from.getWriter();
+ for (int i = 0; i < COUNT; i++) {
+ structWriter.setPosition(i);
+ structWriter.start();
+ ExtensionWriter extensionWriter1 = structWriter.extension("uuid1", UuidType.INSTANCE);
+ extensionWriter1.writeExtension(UUID.randomUUID(), UuidType.INSTANCE);
+ ExtensionWriter extensionWriter2 = structWriter.extension("uuid2", UuidType.INSTANCE);
+ extensionWriter2.writeExtension(UUID.randomUUID(), UuidType.INSTANCE);
+ structWriter.end();
+ }
+
+ from.setValueCount(COUNT);
+
+ // copy values
+ FieldReader in = from.getReader();
+ FieldWriter out = to.getWriter();
+ for (int i = 0; i < COUNT; i++) {
+ in.setPosition(i);
+ out.setPosition(i);
+ ComplexCopier.copy(in, out);
+ }
+ to.setValueCount(COUNT);
+
+ // validate equals
+ assertTrue(VectorEqualsVisitor.vectorEquals(from, to));
+ }
+ }
}
diff --git a/vector/src/test/java/org/apache/arrow/vector/complex/impl/TestPromotableWriter.java b/vector/src/test/java/org/apache/arrow/vector/complex/impl/TestPromotableWriter.java
index 1556852c5a..5b6d65d6ba 100644
--- a/vector/src/test/java/org/apache/arrow/vector/complex/impl/TestPromotableWriter.java
+++ b/vector/src/test/java/org/apache/arrow/vector/complex/impl/TestPromotableWriter.java
@@ -31,6 +31,7 @@
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.vector.DecimalVector;
import org.apache.arrow.vector.DirtyRootAllocator;
+import org.apache.arrow.vector.FieldVector;
import org.apache.arrow.vector.LargeVarBinaryVector;
import org.apache.arrow.vector.LargeVarCharVector;
import org.apache.arrow.vector.UuidVector;
@@ -41,6 +42,7 @@
import org.apache.arrow.vector.complex.StructVector;
import org.apache.arrow.vector.complex.UnionVector;
import org.apache.arrow.vector.complex.writer.BaseWriter.StructWriter;
+import org.apache.arrow.vector.extension.UuidType;
import org.apache.arrow.vector.holders.DurationHolder;
import org.apache.arrow.vector.holders.FixedSizeBinaryHolder;
import org.apache.arrow.vector.holders.NullableDecimalHolder;
@@ -48,15 +50,16 @@
import org.apache.arrow.vector.holders.NullableTimeStampMilliTZHolder;
import org.apache.arrow.vector.holders.TimeStampMilliTZHolder;
import org.apache.arrow.vector.holders.UnionHolder;
+import org.apache.arrow.vector.holders.UuidHolder;
import org.apache.arrow.vector.types.TimeUnit;
import org.apache.arrow.vector.types.Types;
import org.apache.arrow.vector.types.pojo.ArrowType;
import org.apache.arrow.vector.types.pojo.ArrowType.ArrowTypeID;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.FieldType;
-import org.apache.arrow.vector.types.pojo.UuidType;
import org.apache.arrow.vector.util.DecimalUtility;
import org.apache.arrow.vector.util.Text;
+import org.apache.arrow.vector.util.UuidUtility;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -100,7 +103,6 @@ public void testPromoteToUnion() throws Exception {
writer.integer("A").writeInt(10);
// we don't write anything in 3
-
writer.setPosition(4);
writer.integer("A").writeInt(100);
@@ -130,9 +132,21 @@ public void testPromoteToUnion() throws Exception {
binHolder.buffer = buf;
writer.fixedSizeBinary("A", 4).write(binHolder);
+ writer.setPosition(9);
+ UUID uuid = UUID.randomUUID();
+ writer.extension("A", UuidType.INSTANCE).writeExtension(uuid, UuidType.INSTANCE);
+ writer.end();
+
+ writer.setPosition(10);
+ UUID uuid2 = UUID.randomUUID();
+ UuidHolder uuidHolder = new UuidHolder();
+ uuidHolder.buffer = allocator.buffer(UuidType.UUID_BYTE_WIDTH);
+ uuidHolder.buffer.setBytes(0, UuidUtility.getBytesFromUUID(uuid2));
+ writer.extension("A", UuidType.INSTANCE).write(uuidHolder);
writer.end();
+ allocator.releaseBytes(UuidType.UUID_BYTE_WIDTH);
- container.setValueCount(9);
+ container.setValueCount(11);
final UnionVector uv = v.getChild("A", UnionVector.class);
@@ -169,6 +183,12 @@ public void testPromoteToUnion() throws Exception {
.order(ByteOrder.nativeOrder())
.getInt());
+ assertFalse(uv.isNull(9), "9 shouldn't be null");
+ assertEquals(uuid, uv.getObject(9));
+
+ assertFalse(uv.isNull(10), "10 shouldn't be null");
+ assertEquals(uuid2, uv.getObject(10));
+
container.clear();
container.allocateNew();
@@ -785,18 +805,17 @@ public void testExtensionType() throws Exception {
try (final NonNullableStructVector container =
NonNullableStructVector.empty(EMPTY_SCHEMA_PATH, allocator);
final UuidVector v =
- container.addOrGet("uuid", FieldType.nullable(new UuidType()), UuidVector.class);
+ container.addOrGet("uuid", FieldType.nullable(UuidType.INSTANCE), UuidVector.class);
final PromotableWriter writer = new PromotableWriter(v, container)) {
UUID u1 = UUID.randomUUID();
UUID u2 = UUID.randomUUID();
container.allocateNew();
container.setValueCount(1);
- writer.addExtensionTypeWriterFactory(new UuidWriterFactory());
writer.setPosition(0);
- writer.writeExtension(u1);
+ writer.writeExtension(u1, UuidType.INSTANCE);
writer.setPosition(1);
- writer.writeExtension(u2);
+ writer.writeExtension(u2, UuidType.INSTANCE);
container.setValueCount(2);
@@ -805,4 +824,29 @@ public void testExtensionType() throws Exception {
assertEquals(u2, uuidVector.getObject(1));
}
}
+
+ @Test
+ public void testExtensionTypeForList() throws Exception {
+ try (final ListVector container = ListVector.empty(EMPTY_SCHEMA_PATH, allocator);
+ final UuidVector v =
+ (UuidVector)
+ container.addOrGetVector(FieldType.nullable(UuidType.INSTANCE)).getVector();
+ final PromotableWriter writer = new PromotableWriter(v, container)) {
+ UUID u1 = UUID.randomUUID();
+ UUID u2 = UUID.randomUUID();
+ container.allocateNew();
+ container.setValueCount(1);
+
+ writer.setPosition(0);
+ writer.writeExtension(u1, UuidType.INSTANCE);
+ writer.setPosition(1);
+ writer.writeExtension(u2, UuidType.INSTANCE);
+
+ container.setValueCount(2);
+
+ FieldVector uuidVector = container.getDataVector();
+ assertEquals(u1, uuidVector.getObject(0));
+ assertEquals(u2, uuidVector.getObject(1));
+ }
+ }
}
diff --git a/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java b/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java
index 2745386db4..80d03cae6d 100644
--- a/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java
+++ b/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java
@@ -19,6 +19,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.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -31,6 +32,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.UUID;
import org.apache.arrow.memory.ArrowBuf;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.memory.RootAllocator;
@@ -71,9 +73,11 @@
import org.apache.arrow.vector.complex.reader.Float8Reader;
import org.apache.arrow.vector.complex.reader.IntReader;
import org.apache.arrow.vector.complex.writer.BaseWriter.ComplexWriter;
+import org.apache.arrow.vector.complex.writer.BaseWriter.ExtensionWriter;
import org.apache.arrow.vector.complex.writer.BaseWriter.ListWriter;
import org.apache.arrow.vector.complex.writer.BaseWriter.MapWriter;
import org.apache.arrow.vector.complex.writer.BaseWriter.StructWriter;
+import org.apache.arrow.vector.extension.UuidType;
import org.apache.arrow.vector.holders.DecimalHolder;
import org.apache.arrow.vector.holders.DurationHolder;
import org.apache.arrow.vector.holders.FixedSizeBinaryHolder;
@@ -82,8 +86,11 @@
import org.apache.arrow.vector.holders.NullableFixedSizeBinaryHolder;
import org.apache.arrow.vector.holders.NullableTimeStampMilliTZHolder;
import org.apache.arrow.vector.holders.NullableTimeStampNanoTZHolder;
+import org.apache.arrow.vector.holders.NullableUuidHolder;
import org.apache.arrow.vector.holders.TimeStampMilliTZHolder;
+import org.apache.arrow.vector.holders.UuidHolder;
import org.apache.arrow.vector.types.TimeUnit;
+import org.apache.arrow.vector.types.Types.MinorType;
import org.apache.arrow.vector.types.pojo.ArrowType;
import org.apache.arrow.vector.types.pojo.ArrowType.ArrowTypeID;
import org.apache.arrow.vector.types.pojo.ArrowType.Int;
@@ -99,6 +106,7 @@
import org.apache.arrow.vector.util.JsonStringHashMap;
import org.apache.arrow.vector.util.Text;
import org.apache.arrow.vector.util.TransferPair;
+import org.apache.arrow.vector.util.UuidUtility;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -1098,6 +1106,13 @@ public void simpleUnion() throws Exception {
new UnionVector("union", allocator, /* field type */ null, /* call-back */ null);
UnionWriter unionWriter = new UnionWriter(vector);
unionWriter.allocate();
+
+ UUID uuid = UUID.randomUUID();
+ ByteBuffer bb = ByteBuffer.allocate(16);
+ bb.putLong(uuid.getMostSignificantBits());
+ bb.putLong(uuid.getLeastSignificantBits());
+ byte[] uuidByte = bb.array();
+
for (int i = 0; i < COUNT; i++) {
unionWriter.setPosition(i);
if (i % 5 == 0) {
@@ -1120,6 +1135,12 @@ public void simpleUnion() throws Exception {
holder.buffer = buf;
unionWriter.write(holder);
bufs.add(buf);
+ } else if (i % 5 == 4) {
+ UuidHolder holder = new UuidHolder();
+ holder.buffer = allocator.buffer(UuidType.UUID_BYTE_WIDTH);
+ holder.buffer.setBytes(0, uuidByte);
+ unionWriter.write(holder);
+ allocator.releaseBytes(UuidType.UUID_BYTE_WIDTH);
} else {
unionWriter.writeFloat4((float) i);
}
@@ -1145,6 +1166,10 @@ public void simpleUnion() throws Exception {
unionReader.read(holder);
assertEquals(i, holder.buffer.getInt(0));
assertEquals(4, holder.byteWidth);
+ } else if (i % 5 == 4) {
+ NullableUuidHolder holder = new NullableUuidHolder();
+ unionReader.read(holder);
+ assertEquals(UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start), uuid);
} else {
assertEquals((float) i, unionReader.readFloat(), 1e-12);
}
@@ -2489,4 +2514,79 @@ public void unionWithVarCharAndBinaryHelpers() throws Exception {
"row12", new String(vector.getLargeVarBinaryVector().get(11), StandardCharsets.UTF_8));
}
}
+
+ @Test
+ public void extensionWriterReader() throws Exception {
+ // test values
+ UUID u1 = UUID.randomUUID();
+
+ try (NonNullableStructVector parent = NonNullableStructVector.empty("parent", allocator)) {
+ // write
+
+ ComplexWriter writer = new ComplexWriterImpl("root", parent);
+ StructWriter rootWriter = writer.rootAsStruct();
+
+ {
+ ExtensionWriter extensionWriter = rootWriter.extension("uuid1", UuidType.INSTANCE);
+ extensionWriter.setPosition(0);
+ extensionWriter.writeExtension(u1, UuidType.INSTANCE);
+ }
+ // read
+ StructReader rootReader = new SingleStructReaderImpl(parent).reader("root");
+ {
+ FieldReader uuidReader = rootReader.reader("uuid1");
+ uuidReader.setPosition(0);
+ NullableUuidHolder uuidHolder = new NullableUuidHolder();
+ uuidReader.read(uuidHolder);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(uuidHolder.buffer, 0);
+ assertEquals(u1, actualUuid);
+ assertTrue(uuidReader.isSet());
+ assertEquals(uuidReader.getMinorType(), MinorType.EXTENSIONTYPE);
+ assertInstanceOf(UuidType.class, uuidReader.getField().getFieldType().getType());
+ }
+ }
+ }
+
+ @Test
+ void testListOfDenseUnionWriterNPE() {
+ // Regression test for https://github.com/apache/arrow-java/issues/399
+ try (ListVector listVector = ListVector.empty("list", allocator)) {
+ listVector.addOrGetVector(FieldType.nullable(MinorType.DENSEUNION.getType()));
+ UnionListWriter listWriter = listVector.getWriter();
+
+ listWriter.startList();
+ listWriter.endList();
+ }
+ }
+
+ @Test
+ void testListOfDenseUnionWriterWithData() {
+ try (ListVector listVector = ListVector.empty("list", allocator)) {
+ listVector.addOrGetVector(FieldType.nullable(MinorType.DENSEUNION.getType()));
+
+ UnionListWriter listWriter = listVector.getWriter();
+ listWriter.startList();
+ listWriter.writeInt(100);
+ listWriter.writeBigInt(200L);
+ listWriter.endList();
+
+ listWriter.startList();
+ listWriter.writeFloat4(3.14f);
+ listWriter.endList();
+
+ listVector.setValueCount(2);
+
+ assertEquals(2, listVector.getValueCount());
+
+ List> value0 = (List>) listVector.getObject(0);
+ List> value1 = (List>) listVector.getObject(1);
+
+ assertEquals(2, value0.size());
+ assertEquals(100, value0.get(0));
+ assertEquals(200L, value0.get(1));
+
+ assertEquals(1, value1.size());
+ assertEquals(3.14f, value1.get(0));
+ }
+ }
}
diff --git a/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestSimpleWriter.java b/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestSimpleWriter.java
index bf1b9b0dfa..5bb5962704 100644
--- a/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestSimpleWriter.java
+++ b/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestSimpleWriter.java
@@ -20,20 +20,16 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.nio.ByteBuffer;
-import java.util.UUID;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.vector.LargeVarBinaryVector;
import org.apache.arrow.vector.LargeVarCharVector;
-import org.apache.arrow.vector.UuidVector;
import org.apache.arrow.vector.VarBinaryVector;
import org.apache.arrow.vector.VarCharVector;
import org.apache.arrow.vector.complex.impl.LargeVarBinaryWriterImpl;
import org.apache.arrow.vector.complex.impl.LargeVarCharWriterImpl;
-import org.apache.arrow.vector.complex.impl.UuidWriterImpl;
import org.apache.arrow.vector.complex.impl.VarBinaryWriterImpl;
import org.apache.arrow.vector.complex.impl.VarCharWriterImpl;
-import org.apache.arrow.vector.holder.UuidHolder;
import org.apache.arrow.vector.util.Text;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -188,20 +184,4 @@ public void testWriteTextToLargeVarChar() throws Exception {
assertEquals(input, result);
}
}
-
- @Test
- public void testWriteToExtensionVector() throws Exception {
- try (UuidVector vector = new UuidVector("test", allocator);
- UuidWriterImpl writer = new UuidWriterImpl(vector)) {
- UUID uuid = UUID.randomUUID();
- ByteBuffer bb = ByteBuffer.allocate(16);
- bb.putLong(uuid.getMostSignificantBits());
- bb.putLong(uuid.getLeastSignificantBits());
- UuidHolder holder = new UuidHolder();
- holder.value = bb.array();
- writer.write(holder);
- UUID result = vector.getObject(0);
- assertEquals(uuid, result);
- }
- }
}
diff --git a/vector/src/test/java/org/apache/arrow/vector/types/pojo/TestExtensionType.java b/vector/src/test/java/org/apache/arrow/vector/types/pojo/TestExtensionType.java
index d24708d66c..ae5ac0726c 100644
--- a/vector/src/test/java/org/apache/arrow/vector/types/pojo/TestExtensionType.java
+++ b/vector/src/test/java/org/apache/arrow/vector/types/pojo/TestExtensionType.java
@@ -16,6 +16,7 @@
*/
package org.apache.arrow.vector.types.pojo;
+import static org.apache.arrow.vector.TestUtils.ensureRegistered;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -43,10 +44,13 @@
import org.apache.arrow.vector.Float4Vector;
import org.apache.arrow.vector.UuidVector;
import org.apache.arrow.vector.ValueIterableVector;
+import org.apache.arrow.vector.ValueVector;
import org.apache.arrow.vector.VectorSchemaRoot;
import org.apache.arrow.vector.compare.Range;
import org.apache.arrow.vector.compare.RangeEqualsVisitor;
import org.apache.arrow.vector.complex.StructVector;
+import org.apache.arrow.vector.complex.writer.FieldWriter;
+import org.apache.arrow.vector.extension.UuidType;
import org.apache.arrow.vector.ipc.ArrowFileReader;
import org.apache.arrow.vector.ipc.ArrowFileWriter;
import org.apache.arrow.vector.types.FloatingPointPrecision;
@@ -59,9 +63,9 @@ public class TestExtensionType {
/** Test that a custom UUID type can be round-tripped through a temporary file. */
@Test
public void roundtripUuid() throws IOException {
- ExtensionTypeRegistry.register(new UuidType());
+ ensureRegistered(UuidType.INSTANCE);
final Schema schema =
- new Schema(Collections.singletonList(Field.nullable("a", new UuidType())));
+ new Schema(Collections.singletonList(Field.nullable("a", UuidType.INSTANCE)));
try (final BufferAllocator allocator = new RootAllocator(Integer.MAX_VALUE);
final VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) {
UUID u1 = UUID.randomUUID();
@@ -89,7 +93,7 @@ public void roundtripUuid() throws IOException {
assertEquals(root.getSchema(), readerRoot.getSchema());
final Field field = readerRoot.getSchema().getFields().get(0);
- final UuidType expectedType = new UuidType();
+ final UuidType expectedType = UuidType.INSTANCE;
assertEquals(
field.getMetadata().get(ExtensionType.EXTENSION_METADATA_KEY_NAME),
expectedType.extensionName());
@@ -113,9 +117,9 @@ public void roundtripUuid() throws IOException {
/** Test that a custom UUID type can be read as its underlying type. */
@Test
public void readUnderlyingType() throws IOException {
- ExtensionTypeRegistry.register(new UuidType());
+ ensureRegistered(UuidType.INSTANCE);
final Schema schema =
- new Schema(Collections.singletonList(Field.nullable("a", new UuidType())));
+ new Schema(Collections.singletonList(Field.nullable("a", UuidType.INSTANCE)));
try (final BufferAllocator allocator = new RootAllocator(Integer.MAX_VALUE);
final VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) {
UUID u1 = UUID.randomUUID();
@@ -135,7 +139,7 @@ public void readUnderlyingType() throws IOException {
writer.end();
}
- ExtensionTypeRegistry.unregister(new UuidType());
+ ExtensionTypeRegistry.unregister(UuidType.INSTANCE);
try (final SeekableByteChannel channel =
Files.newByteChannel(Paths.get(file.getAbsolutePath()));
@@ -153,7 +157,7 @@ public void readUnderlyingType() throws IOException {
.getByteWidth());
final Field field = readerRoot.getSchema().getFields().get(0);
- final UuidType expectedType = new UuidType();
+ final UuidType expectedType = UuidType.INSTANCE;
assertEquals(
field.getMetadata().get(ExtensionType.EXTENSION_METADATA_KEY_NAME),
expectedType.extensionName());
@@ -254,7 +258,7 @@ public void roundtripLocation() throws IOException {
@Test
public void testVectorCompare() {
- UuidType uuidType = new UuidType();
+ UuidType uuidType = UuidType.INSTANCE;
ExtensionTypeRegistry.register(uuidType);
try (final BufferAllocator allocator = new RootAllocator(Integer.MAX_VALUE);
UuidVector a1 =
@@ -331,6 +335,11 @@ public String serialize() {
public FieldVector getNewVector(String name, FieldType fieldType, BufferAllocator allocator) {
return new LocationVector(name, allocator);
}
+
+ @Override
+ public FieldWriter getNewFieldWriter(ValueVector vector) {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
}
public static class LocationVector extends ExtensionTypeVector
diff --git a/vector/src/test/java/org/apache/arrow/vector/types/pojo/UuidType.java b/vector/src/test/java/org/apache/arrow/vector/types/pojo/UuidType.java
deleted file mode 100644
index 5e2bd8881b..0000000000
--- a/vector/src/test/java/org/apache/arrow/vector/types/pojo/UuidType.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.vector.types.pojo;
-
-import org.apache.arrow.memory.BufferAllocator;
-import org.apache.arrow.vector.FieldVector;
-import org.apache.arrow.vector.FixedSizeBinaryVector;
-import org.apache.arrow.vector.UuidVector;
-import org.apache.arrow.vector.types.pojo.ArrowType.ExtensionType;
-
-public class UuidType extends ExtensionType {
-
- @Override
- public ArrowType storageType() {
- return new ArrowType.FixedSizeBinary(16);
- }
-
- @Override
- public String extensionName() {
- return "uuid";
- }
-
- @Override
- public boolean extensionEquals(ExtensionType other) {
- return other instanceof UuidType;
- }
-
- @Override
- public ArrowType deserialize(ArrowType storageType, String serializedData) {
- if (!storageType.equals(storageType())) {
- throw new UnsupportedOperationException(
- "Cannot construct UuidType from underlying type " + storageType);
- }
- return new UuidType();
- }
-
- @Override
- public String serialize() {
- return "";
- }
-
- @Override
- public FieldVector getNewVector(String name, FieldType fieldType, BufferAllocator allocator) {
- return new UuidVector(name, allocator, new FixedSizeBinaryVector(name, allocator, 16));
- }
-}
diff --git a/vector/src/test/java/org/apache/arrow/vector/util/TestVectorAppender.java b/vector/src/test/java/org/apache/arrow/vector/util/TestVectorAppender.java
index 4ee9630a4d..9a8143f51b 100644
--- a/vector/src/test/java/org/apache/arrow/vector/util/TestVectorAppender.java
+++ b/vector/src/test/java/org/apache/arrow/vector/util/TestVectorAppender.java
@@ -26,10 +26,13 @@
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
+import org.apache.arrow.memory.ArrowBuf;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.memory.util.CommonUtil;
+import org.apache.arrow.vector.BaseLargeVariableWidthVector;
import org.apache.arrow.vector.BaseValueVector;
+import org.apache.arrow.vector.BaseVariableWidthVector;
import org.apache.arrow.vector.BaseVariableWidthViewVector;
import org.apache.arrow.vector.BigIntVector;
import org.apache.arrow.vector.BitVector;
@@ -47,11 +50,13 @@
import org.apache.arrow.vector.complex.FixedSizeListVector;
import org.apache.arrow.vector.complex.LargeListVector;
import org.apache.arrow.vector.complex.ListVector;
+import org.apache.arrow.vector.complex.RunEndEncodedVector;
import org.apache.arrow.vector.complex.StructVector;
import org.apache.arrow.vector.complex.UnionVector;
import org.apache.arrow.vector.holders.NullableBigIntHolder;
import org.apache.arrow.vector.holders.NullableFloat4Holder;
import org.apache.arrow.vector.holders.NullableIntHolder;
+import org.apache.arrow.vector.ipc.message.ArrowFieldNode;
import org.apache.arrow.vector.testing.ValueVectorDataPopulator;
import org.apache.arrow.vector.types.Types;
import org.apache.arrow.vector.types.pojo.ArrowType;
@@ -177,6 +182,82 @@ public void testAppendVariableWidthVector() {
}
}
+ @Test
+ public void testAppendVariableWidthVectorWithNonZeroStartOffset() {
+ try (VarCharVector target = new VarCharVector("", allocator);
+ VarCharVector delta = new VarCharVector("", allocator)) {
+
+ target.allocateNew(64, 4);
+ ValueVectorDataPopulator.setVector(target, "a0", "a1");
+
+ // Build a delta vector whose offset buffer does not start at zero, as produced e.g. by
+ // importing a sliced array through the C data interface. The values are "BBBB" and
+ // "CCCC"; the data buffer additionally holds 4 bytes of unreferenced prefix ("AAAA").
+ try (ArrowBuf validity = allocator.buffer(1);
+ ArrowBuf offsets = allocator.buffer(12);
+ ArrowBuf data = allocator.buffer(12)) {
+ validity.setByte(0, 0b11);
+ offsets.setInt(0, 4);
+ offsets.setInt(4, 8);
+ offsets.setInt(8, 12);
+ data.setBytes(0, "AAAABBBBCCCC".getBytes(StandardCharsets.UTF_8));
+ delta.loadFieldBuffers(new ArrowFieldNode(2, 0), Arrays.asList(validity, offsets, data));
+ }
+
+ VectorAppender appender = new VectorAppender(target);
+ delta.accept(appender, null);
+
+ // the unreferenced prefix must not be appended
+ assertEquals(
+ 4 + 8,
+ target
+ .getOffsetBuffer()
+ .getInt((long) target.getValueCount() * BaseVariableWidthVector.OFFSET_WIDTH));
+
+ try (VarCharVector expected = new VarCharVector("expected", allocator)) {
+ expected.allocateNew();
+ ValueVectorDataPopulator.setVector(expected, "a0", "a1", "BBBB", "CCCC");
+ assertVectorsEqual(expected, target);
+ }
+ }
+ }
+
+ @Test
+ public void testAppendLargeVariableWidthVectorWithNonZeroStartOffset() {
+ try (LargeVarCharVector target = new LargeVarCharVector("", allocator);
+ LargeVarCharVector delta = new LargeVarCharVector("", allocator)) {
+
+ target.allocateNew(64, 4);
+ ValueVectorDataPopulator.setVector(target, "a0", "a1");
+
+ try (ArrowBuf validity = allocator.buffer(1);
+ ArrowBuf offsets = allocator.buffer(24);
+ ArrowBuf data = allocator.buffer(12)) {
+ validity.setByte(0, 0b11);
+ offsets.setLong(0, 4);
+ offsets.setLong(8, 8);
+ offsets.setLong(16, 12);
+ data.setBytes(0, "AAAABBBBCCCC".getBytes(StandardCharsets.UTF_8));
+ delta.loadFieldBuffers(new ArrowFieldNode(2, 0), Arrays.asList(validity, offsets, data));
+ }
+
+ VectorAppender appender = new VectorAppender(target);
+ delta.accept(appender, null);
+
+ assertEquals(
+ 4 + 8,
+ target
+ .getOffsetBuffer()
+ .getLong((long) target.getValueCount() * BaseLargeVariableWidthVector.OFFSET_WIDTH));
+
+ try (LargeVarCharVector expected = new LargeVarCharVector("expected", allocator)) {
+ expected.allocateNew();
+ ValueVectorDataPopulator.setVector(expected, "a0", "a1", "BBBB", "CCCC");
+ assertVectorsEqual(expected, target);
+ }
+ }
+ }
+
@Test
public void testAppendVariableWidthViewVector() {
final int length1 = 10;
@@ -430,6 +511,115 @@ public void testAppendListVector() {
}
}
+ @Test
+ public void testAppendListVectorWithNonZeroStartOffset() {
+ try (ListVector target = ListVector.empty("target", allocator);
+ ListVector delta = ListVector.empty("delta", allocator)) {
+
+ target.allocateNew();
+ ValueVectorDataPopulator.setVector(target, Arrays.asList(0, 1), Arrays.asList(2, 3));
+
+ // Build a delta vector whose offset buffer does not start at zero, as produced e.g. by
+ // importing a sliced array through the C data interface: lists [10, 11] and [12, 13],
+ // with one unreferenced prefix element (9) in the data vector.
+ delta.addOrGetVector(FieldType.nullable(Types.MinorType.INT.getType()));
+ IntVector deltaDataVector = (IntVector) delta.getDataVector();
+ deltaDataVector.allocateNew(5);
+ for (int i = 0; i < 5; i++) {
+ deltaDataVector.set(i, 9 + i);
+ }
+ deltaDataVector.setValueCount(5);
+ try (ArrowBuf validity = allocator.buffer(1);
+ ArrowBuf offsets = allocator.buffer(12)) {
+ validity.setByte(0, 0b11);
+ offsets.setInt(0, 1);
+ offsets.setInt(4, 3);
+ offsets.setInt(8, 5);
+ delta.loadFieldBuffers(new ArrowFieldNode(2, 0), Arrays.asList(validity, offsets));
+ }
+ assertEquals(Arrays.asList(10, 11), delta.getObject(0));
+
+ VectorAppender appender = new VectorAppender(target);
+ delta.accept(appender, null);
+
+ assertEquals(4, target.getValueCount());
+ // the unreferenced prefix element must not be appended
+ assertEquals(
+ 4 + 4,
+ target.getOffsetBuffer().getInt((long) target.getValueCount() * ListVector.OFFSET_WIDTH));
+ assertEquals(Arrays.asList(0, 1), target.getObject(0));
+ assertEquals(Arrays.asList(2, 3), target.getObject(1));
+ assertEquals(Arrays.asList(10, 11), target.getObject(2));
+ assertEquals(Arrays.asList(12, 13), target.getObject(3));
+ }
+ }
+
+ @Test
+ public void testAppendLargeListVector() {
+ try (LargeListVector target = LargeListVector.empty("target", allocator);
+ LargeListVector delta = LargeListVector.empty("delta", allocator)) {
+
+ target.allocateNew();
+ ValueVectorDataPopulator.setVector(target, Arrays.asList(0, 1), null, Arrays.asList(4, 5));
+
+ delta.allocateNew();
+ ValueVectorDataPopulator.setVector(delta, Arrays.asList(10, 11, 12), Arrays.asList(13, 14));
+
+ VectorAppender appender = new VectorAppender(target);
+ delta.accept(appender, null);
+
+ assertEquals(5, target.getValueCount());
+ assertEquals(Arrays.asList(0, 1), target.getObject(0));
+ assertTrue(target.isNull(1));
+ assertEquals(Arrays.asList(4, 5), target.getObject(2));
+ assertEquals(Arrays.asList(10, 11, 12), target.getObject(3));
+ assertEquals(Arrays.asList(13, 14), target.getObject(4));
+ }
+ }
+
+ @Test
+ public void testAppendLargeListVectorWithNonZeroStartOffset() {
+ try (LargeListVector target = LargeListVector.empty("target", allocator);
+ LargeListVector delta = LargeListVector.empty("delta", allocator)) {
+
+ target.allocateNew();
+ ValueVectorDataPopulator.setVector(target, Arrays.asList(0, 1), Arrays.asList(2, 3));
+
+ // same as testAppendListVectorWithNonZeroStartOffset, with 8-byte offsets
+ delta.addOrGetVector(FieldType.nullable(Types.MinorType.INT.getType()));
+ IntVector deltaDataVector = (IntVector) delta.getDataVector();
+ deltaDataVector.allocateNew(5);
+ for (int i = 0; i < 5; i++) {
+ deltaDataVector.set(i, 9 + i);
+ }
+ deltaDataVector.setValueCount(5);
+ try (ArrowBuf validity = allocator.buffer(1);
+ ArrowBuf offsets = allocator.buffer(24)) {
+ validity.setByte(0, 0b11);
+ offsets.setLong(0, 1);
+ offsets.setLong(8, 3);
+ offsets.setLong(16, 5);
+ delta.loadFieldBuffers(new ArrowFieldNode(2, 0), Arrays.asList(validity, offsets));
+ }
+ assertEquals(Arrays.asList(10, 11), delta.getObject(0));
+
+ VectorAppender appender = new VectorAppender(target);
+ delta.accept(appender, null);
+
+ assertEquals(4, target.getValueCount());
+ // the unreferenced prefix element must not be appended
+ assertEquals(
+ 4 + 4,
+ target
+ .getOffsetBuffer()
+ .getLong((long) target.getValueCount() * LargeListVector.OFFSET_WIDTH));
+ assertEquals(Arrays.asList(0, 1), target.getObject(0));
+ assertEquals(Arrays.asList(2, 3), target.getObject(1));
+ assertEquals(Arrays.asList(10, 11), target.getObject(2));
+ assertEquals(Arrays.asList(12, 13), target.getObject(3));
+ }
+ }
+
@Test
public void testAppendEmptyListVector() {
try (ListVector target = ListVector.empty("target", allocator);
@@ -1025,6 +1215,72 @@ public void testAppendDenseUnionVectorMismatch() {
}
}
+ @Test
+ public void testAppendRunEndEncodedVector() {
+ final FieldType reeFieldType = FieldType.notNullable(ArrowType.RunEndEncoded.INSTANCE);
+ final Field runEndsField =
+ new Field("runEnds", FieldType.notNullable(Types.MinorType.INT.getType()), null);
+ final Field valuesField = Field.nullable("values", Types.MinorType.INT.getType());
+ final List children = Arrays.asList(runEndsField, valuesField);
+
+ final Field targetField = new Field("target", reeFieldType, children);
+ final Field deltaField = new Field("delta", reeFieldType, children);
+ try (RunEndEncodedVector target = new RunEndEncodedVector(targetField, allocator, null);
+ RunEndEncodedVector delta = new RunEndEncodedVector(deltaField, allocator, null)) {
+
+ // populate target
+ target.allocateNew();
+ // data: [1, 1, 2, null, 3, 3, 3] (7 values)
+ // values: [1, 2, null, 3]
+ // runEnds: [2, 3, 4, 7]
+ ValueVectorDataPopulator.setVector((IntVector) target.getValuesVector(), 1, 2, null, 3);
+ ValueVectorDataPopulator.setVector((IntVector) target.getRunEndsVector(), 2, 3, 4, 7);
+ target.setValueCount(7);
+
+ // populate delta
+ delta.allocateNew();
+ // data: [3, 4, 4, 5, null, null] (6 values)
+ // values: [3, 4, 5, null]
+ // runEnds: [1, 3, 4, 6]
+ ValueVectorDataPopulator.setVector((IntVector) delta.getValuesVector(), 3, 4, 5, null);
+ ValueVectorDataPopulator.setVector((IntVector) delta.getRunEndsVector(), 1, 3, 4, 6);
+ delta.setValueCount(6);
+
+ VectorAppender appender = new VectorAppender(target);
+ delta.accept(appender, null);
+
+ assertEquals(13, target.getValueCount());
+
+ final Field expectedField = new Field("expected", reeFieldType, children);
+ try (RunEndEncodedVector expected = new RunEndEncodedVector(expectedField, allocator, null)) {
+ expected.allocateNew();
+ // expected data: [1, 1, 2, null, 3, 3, 3, 3, 4, 4, 5, null, null] (13 values)
+ // expected values: [1, 2, null, 3, 3, 4, 5, null]
+ // expected runEnds: [2, 3, 4, 7, 8, 10, 11, 13]
+ ValueVectorDataPopulator.setVector(
+ (IntVector) expected.getValuesVector(), 1, 2, null, 3, 3, 4, 5, null);
+ ValueVectorDataPopulator.setVector(
+ (IntVector) expected.getRunEndsVector(), 2, 3, 4, 7, 8, 10, 11, 13);
+ expected.setValueCount(13);
+
+ assertVectorsEqual(expected, target);
+ }
+
+ // Check that delta is unchanged.
+ final Field expectedDeltaField = new Field("expectedDelta", reeFieldType, children);
+ try (RunEndEncodedVector expectedDelta =
+ new RunEndEncodedVector(expectedDeltaField, allocator, null)) {
+ expectedDelta.allocateNew();
+ ValueVectorDataPopulator.setVector(
+ (IntVector) expectedDelta.getValuesVector(), 3, 4, 5, null);
+ ValueVectorDataPopulator.setVector(
+ (IntVector) expectedDelta.getRunEndsVector(), 1, 3, 4, 6);
+ expectedDelta.setValueCount(6);
+ assertVectorsEqual(expectedDelta, delta);
+ }
+ }
+ }
+
@Test
public void testAppendVectorNegative() {
final int vectorLength = 10;