From 8d1802cef6bacd27d1cc8a0c2cf3efcddb6bf255 Mon Sep 17 00:00:00 2001 From: Kaustav Sarkar <70840177+Kaustav-Sarkar@users.noreply.github.com> Date: Mon, 29 Dec 2025 14:01:10 +0530 Subject: [PATCH 001/171] GH-399: Check for null writers in DenseUnionWriter#setPosition (#938) ## GH-399 Fix setPosition fails with NullPointerException Fixed a `NullPointerException` in `DenseUnionWriter#setPosition`. The issue was that `setPosition` tried to update all writers in its internal array, even if they hadn't been initialized yet. I added a null check so it only updates writers that actually exist. Also added a regression test (`TestDenseUnionWriterNPE`) to verify the fix and updated `.gitignore`. Closes #399. --- .gitignore | 2 + .../codegen/templates/DenseUnionWriter.java | 4 +- .../complex/writer/TestComplexWriter.java | 43 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) 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/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/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java b/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java index 46c259bda0..871a3cc461 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 @@ -2530,4 +2530,47 @@ public void extensionWriterReader() throws Exception { } } } + + @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)); + } + } } From 385b51eb5bf001e34d71d63b5980a5eabaa38294 Mon Sep 17 00:00:00 2001 From: David Li
Sourced from org.checkerframework:checker-qual's releases.
Checker Framework 3.52.1
Version 3.52.1 (2025-12-02)
User-visible changes:
Added
Opt.ifPresentOrElse()method.
Sourced from org.checkerframework:checker-qual's changelog.
Version 3.52.1 (2025-12-02)
User-visible changes:
Added
Opt.ifPresentOrElse()method.
72c0de5
new release 3.52.10549f00
Remove link.85842ab
Prep for release.294c7ca
Fix the dataflow shaded jars that are published. (#7404)cd7c953
Update cimg/base Docker tag to v2025.12 (#7403)026bd52
Link from the developer manual to "building from source" in
the manual (#7385)e086cba
More signature annotations31ca5d3
Correct shaded dataflow jars. (#7402)08cc5d1
Update dependency com.amazonaws:aws-java-sdk-bom to v1.12.794 (#7401)4a22556
Nullness annotations for java.lang.classfileSourced from org.checkerframework:checker's releases.
Checker Framework 3.52.1
Version 3.52.1 (2025-12-02)
User-visible changes:
Added
Opt.ifPresentOrElse()method.
Sourced from org.checkerframework:checker's changelog.
Version 3.52.1 (2025-12-02)
User-visible changes:
Added
Opt.ifPresentOrElse()method.
72c0de5
new release 3.52.10549f00
Remove link.85842ab
Prep for release.294c7ca
Fix the dataflow shaded jars that are published. (#7404)cd7c953
Update cimg/base Docker tag to v2025.12 (#7403)026bd52
Link from the developer manual to "building from source" in
the manual (#7385)e086cba
More signature annotations31ca5d3
Correct shaded dataflow jars. (#7402)08cc5d1
Update dependency com.amazonaws:aws-java-sdk-bom to v1.12.794 (#7401)4a22556
Nullness annotations for java.lang.classfileSourced from org.jacoco:jacoco-maven-plugin's releases.
0.8.14
New Features
- JaCoCo now officially supports Java 25 (GitHub #1950).
- Experimental support for Java 26 class files (GitHub #1870).
- Branches added by the Kotlin compiler for default argument number 33 or higher are filtered out during generation of report (GitHub #1655).
- Part of bytecode generated by the Kotlin compiler for elvis operator that follows safe call operator is filtered out during generation of report (GitHub #1814, #1954).
- Part of bytecode generated by the Kotlin compiler for more cases of chained safe call operators is filtered out during generation of report (GitHub #1956).
- Part of bytecode generated by the Kotlin compiler for invocations of suspendCoroutineUninterceptedOrReturn intrinsic is filtered out during generation of report (GitHub #1929).
- Part of bytecode generated by the Kotlin compiler for suspending lambdas with parameters is filtered out during generation of report (GitHub #1945).
- Part of bytecode generated by the Kotlin compiler for suspending functions and lambdas with suspension points that return inline value class is filtered out during generation of report (GitHub #1871).
- Part of bytecode generated by the Kotlin Compose compiler plugin for pausable composition is filtered out during generation of report (GitHub #1911).
- Methods generated by the Kotlin serialization compiler plugin are filtered out (GitHub #1885, #1970, #1971).
Fixed bugs
- Fixed handling of implicit else clause of when with String subject in Kotlin (GitHub #1813, #1940).
- Fixed handling of implicit default clause of switch by String in Java when compiled by ECJ (GitHub #1813, #1940). Fixed handling of exceptions in chains of safe call operators in Kotlin (GitHub #1819).
Non-functional Changes
- JaCoCo now depends on ASM 9.9 (GitHub #1965).
2eb2483
Prepare release v0.8.14de76181
KotlinSerializableFilter should filter more methods (#1971)89c4bd5
Fix NPE in KotlinSerializableFilter (#1970)0981128
Migrate release staging to the Central Publisher Portal (#1968)d07bc6b
Add filter for bytecode generated by Kotlin serialization compiler
plugin (#1...5e35fd5
Upgrade maven-dependency-plugin to 3.9.0 (#1966)c2fe5cc
Upgrade ASM to 9.9 (#1965)b0f8e23
KotlinSafeCallOperatorFilter should filter "unoptimized" safe
call followed b...c7bd3f4
Upgrade spotless-maven-plugin to 3.0.0 (#1961)faa289d
KotlinSafeCallOperatorFilter should not be affected by presence of
pseudo ins...... (truncated)
2148f28
v2.11.7e65fa80
#1614
investigating "duplicate" nullable annotationsd2ac4f1
Merge pull request #1616
from werli/fix-build-with-optional1470f17
Conditionally remove unnecessary cast for optional record wither
methodsc56f082
#1611
#1579
advancing hacks and workarounds for type_use / nullable annotations95df0cd
Custom nullable in nullableAnnotation should not use
qualified notationf7a662e
#1610
derived arrays, nullable array cloning9001c82
#1612
false negative in testeecbc5b
#1612
fixing and refining no-arg constructorsb659a65
whatever to buildSourced from com.google.api.grpc:proto-google-common-protos's releases.
v2.63.0
2.63.0 (2025-10-16)
Features
- librariangen: add bazel package (#3940) (feabef3)
- librariangen: add execv package (#3932) (f6b0b47)
- librariangen: add protoc package (#3935) (598de06)
- librariangen: add request package (#3933) (fe44aed)
- librariangen: scaffold Java language container for Librarian (#3926) (29d188d)
Dependencies
- Bump errorprone-annotations to v2.42.0 (8d6c1f9)
- Bump guava to v33.5.0 (8d6c1f9)
- Bump j2objc-annotations to v3.1 (8d6c1f9)
- update google auth library dependencies to v1.40.0 (#3945) (1d74663)
- Upgrade Google Http Java Client to v2.0.2 (#3946) (7fb4f15)
v2.62.3
2.62.3 (2025-10-02)
Bug Fixes
v2.62.2
2.62.2 (2025-09-18)
Dependencies
- update google api dependencies (#3885) (3694fed)
- update google auth library dependencies to v1.39.1 (#3907) (e86a4f6)
v2.62.1
2.62.1 (2025-09-05)
Dependencies
v2.62.0
2.62.0 (2025-08-19)
... (truncated)
Sourced from com.google.api.grpc:proto-google-common-protos's changelog.
Changelog
2.64.1 (2025-11-07)
Dependencies
2.64.0 (2025-10-31)
Features
- [common-protos] Add
Carouselwidget (1e4a7e5)- librariangen: add generate package (#3952) (2f6c75d)
- librariangen: generate grpc stubs and resource helpers (#3967) (452d703)
Dependencies
2.63.0 (2025-10-16)
Features
- librariangen: add bazel package (#3940) (feabef3)
- librariangen: add execv package (#3932) (f6b0b47)
- librariangen: add protoc package (#3935) (598de06)
- librariangen: add request package (#3933) (fe44aed)
- librariangen: scaffold Java language container for Librarian (#3926) (29d188d)
Dependencies
- Bump errorprone-annotations to v2.42.0 (8d6c1f9)
- Bump guava to v33.5.0 (8d6c1f9)
- Bump j2objc-annotations to v3.1 (8d6c1f9)
- update google auth library dependencies to v1.40.0 (#3945) (1d74663)
- Upgrade Google Http Java Client to v2.0.2 (#3946) (7fb4f15)
2.62.3 (2025-10-02)
Bug Fixes
... (truncated)
4aaea1e
chore(main): release 2.55.1 (#3695)2725744
deps: revert "deps: update arrow.version to v18.2.0" (#3694)3d06ab7
chore(main): release 2.55.1-SNAPSHOT (#3692)a38020a
chore(main): release 2.55.0 (#3669)8fd7b62
build(deps): update dependency
com.google.cloud:google-cloud-shared-config to...2562a7d
chore: update googleapis commit at Thu Feb 27 02:27:38 UTC 2025 (#3666)542d98d
chore: add aliases to generate command options. (#3689)5192426
chore: add java 8 compatibility check (#3688)25d3101
chore: fix logback-classic version for testing (#3686)0932605
test: Reduce the LRO timeout value in Showcase tests (#3684)511cbac
[maven-release-plugin] prepare release netty-4.2.7.Finalbf1cad6
Adjust plugin config to not publish testsuite artifacts690f56f
[maven-release-plugin] rollback the release of netty-4.2.7.Final63b5232
[maven-release-plugin] prepare for next development iteration9f99dfd
[maven-release-plugin] prepare release netty-4.2.7.Final551c32a
Upgrade publishing plugina6660fe
[maven-release-plugin] rollback the release of netty-4.2.7.Final297b7c1
[maven-release-plugin] prepare for next development iteration2c89a17
[maven-release-plugin] prepare release netty-4.2.7.Final1782e8c
Merge commit from forkSourced from org.apache.parquet:parquet-avro's releases.
Apache Parquet Java 1.16.0
What's Changed
- GH-2943: Remove hadoop-2 support by
@steveloughranin apache/parquet-java#3061- MINOR: Use
exec-maven-plugin.versionproperty by@Fokkoin apache/parquet-java#3047- MINOR: Add shading for JDK22 specific classes by
@Fokkoin apache/parquet-java#3081- MINOR: Revert
buildnumber-maven-pluginto 3.2.0 by@Fokkoin apache/parquet-java#3082- GH-3086: Allow for empty beans by
@Fokkoin apache/parquet-java#3087- GH-3089: Add missing license header to pom.xml by
@raulcdin apache/parquet-java#3090- GH-3078: Use Hadoop FileSystem.openFile() to open files by
@steveloughranin apache/parquet-java#3079- MINOR: Bump version to 1.16.0-SNAPSHOT by
@wgtmacin apache/parquet-java#3097- Bump org.codehaus.mojo:exec-maven-plugin from 3.3.0 to 3.5.0 by
@dependabot[bot] in apache/parquet-java#3092- Bump commons-logging:commons-logging from 1.3.3 to 1.3.4 by
@dependabot[bot] in apache/parquet-java#3094- Bump net.openhft:zero-allocation-hashing from 0.26ea0 to 0.27ea0 by
@dependabot[bot] in apache/parquet-java#3093- Bump com.google.api.grpc:proto-google-common-protos from 2.41.0 to 2.50.0 by
@dependabot[bot] in apache/parquet-java#3109- Bump jackson.version from 2.18.1 to 2.18.2 by
@dependabot[bot] in apache/parquet-java#3108- MINOR: Remove
scalaproperties frompom.xmlby@Fokkoin apache/parquet-java#3104- GH-3114: Fix LogicalType conversions for nested records on Avro <= 1.8 by
@clairemcgintyin apache/parquet-java#3111- Bump com.google.truth.extensions:truth-proto-extension from 1.4.3 to 1.4.4 by
@dependabot[bot] in apache/parquet-java#3107- Bump org.cyclonedx:cyclonedx-maven-plugin from 2.8.0 to 2.9.1 by
@dependabot[bot] in apache/parquet-java#3120- Bump org.apache.commons:commons-text from 1.12.0 to 1.13.0 by
@dependabot[bot] in apache/parquet-java#3119- MINOR: Remove Joda as a direct dependency by
@Fokkoin apache/parquet-java#3132- Bump org.easymock:easymock from 5.4.0 to 5.5.0 by
@dependabot[bot] in apache/parquet-java#3131- GH-3099 add libthrift to parquet-cli shaded jar by
@Arnaud-Nauwynckin apache/parquet-java#3100- GH-3127: Enabled
parquet.hadoop.vectored.io.enabledby default by@dongjoon-hyunin apache/parquet-java#3128- GH-3123: Omit level histogram for some max levels by
@wgtmacin apache/parquet-java#3124- GH-3133: Fix SizeStatistics to handle omitted histogram by
@wgtmacin apache/parquet-java#3134- GH-3125: Add CLI for SizeStatistics by
@wgtmacin apache/parquet-java#3126- GH-3115-Fix int96 read issue in complex type by
@pratyush-sharma-2025in apache/parquet-java#3118- MINOR: Remove
parquet-toolsfromNOTICEby@Fokkoin apache/parquet-java#3140- Bump com.google.guava:guava from 33.2.1-jre to 33.4.0-jre by
@dependabot[bot] in apache/parquet-java#3137- Bump protobuf.version from 3.25.5 to 3.25.6 by
@dependabot[bot] in apache/parquet-java#3138- MINOR: Improve exception message in InternalFileDecryptor by
@zhongyujiangin apache/parquet-java#3143- Bump com.google.api.grpc:proto-google-common-protos from 2.50.0 to 2.51.0 by
@dependabot[bot] in apache/parquet-java#3151- MINOR: Remove release script by
@Fokkoin apache/parquet-java#3144- Deprecate Apache Pig integration by
@Fokkoin apache/parquet-java#3153- Bump com.h2database:h2 from 2.3.230 to 2.3.232 by
@dependabot[bot] in apache/parquet-java#3158- Bump commons-logging:commons-logging from 1.3.4 to 1.3.5 by
@dependabot[bot] in apache/parquet-java#3159- Add logical type annotation for
UnknownTypeby@Fokkoin apache/parquet-java#3154- GH-3156: Enable vectored IO by default. by
@ahmarsuhailin apache/parquet-java#3155- Bump it.unimi.dsi:fastutil from 8.5.13 to 8.5.15 by
@dependabot[bot] in apache/parquet-java#3162- GH-3122: Correct V2 page header compression fields for zero-size data pages by
@ConeyLiuin apache/parquet-java#3148- GH-3163: Reduce memory and time overhead of ParquetRewriterTests by
@rahulketchin apache/parquet-java#3164- MINOR: Reader fails fast when footer size is larger than INT_MAX by
@ConeyLiuin apache/parquet-java#3136- GH-3168: Restrict trusted packages in the parquet-avro module by
@wgtmacin apache/parquet-java#3169- GH-3172: Do not drop blocks with some null values if
DictionaryFilteris applied forUserDefinedPredicatewhich keeps null values by@ebartkusin apache/parquet-java#3173- Bump jackson.version from 2.18.2 to 2.18.3 by
@dependabot[bot] in apache/parquet-java#3170- MINOR: update latest version to 1.15.1 by
@wgtmacin apache/parquet-java#3179- Bump com.google.api.grpc:proto-google-common-protos from 2.51.0 to 2.54.1 by
@dependabot[bot] in apache/parquet-java#3177- Bump Parquet Format to 2.11 by
@Fokkoin apache/parquet-java#3181- [MINOR] Enable jitpack.io repo only when brotli is required by
@pan3793in apache/parquet-java#3180
... (truncated)
402c381
[maven-release-plugin] prepare release apache-parquet-1.16.0-rc20e279ef
Add comparator for UnknownLogicalType (#3292)
(#3295)f85f083
[maven-release-plugin] prepare for next development iteration36d1880
[maven-release-plugin] prepare release apache-parquet-1.16.0-rc12d463ee
[maven-release-plugin] prepare for next development iteration1e3d701
[maven-release-plugin] prepare release apache-parquet-1.16.0-rc07ef2f91
bump parquet-plugins to 1.16.0 for release0d25e13
MINOR: Bump parquet-format to 2.12.0 (#3285)299b0ae
MINOR: Bump thrift to 0.22.0 (#3229)36a5f9c
Bump jackson.version from 2.19.0 to 2.19.2 (#3266)Sourced from org.apache.parquet:parquet-hadoop's releases.
Apache Parquet Java 1.16.0
What's Changed
- GH-2943: Remove hadoop-2 support by
@steveloughranin apache/parquet-java#3061- MINOR: Use
exec-maven-plugin.versionproperty by@Fokkoin apache/parquet-java#3047- MINOR: Add shading for JDK22 specific classes by
@Fokkoin apache/parquet-java#3081- MINOR: Revert
buildnumber-maven-pluginto 3.2.0 by@Fokkoin apache/parquet-java#3082- GH-3086: Allow for empty beans by
@Fokkoin apache/parquet-java#3087- GH-3089: Add missing license header to pom.xml by
@raulcdin apache/parquet-java#3090- GH-3078: Use Hadoop FileSystem.openFile() to open files by
@steveloughranin apache/parquet-java#3079- MINOR: Bump version to 1.16.0-SNAPSHOT by
@wgtmacin apache/parquet-java#3097- Bump org.codehaus.mojo:exec-maven-plugin from 3.3.0 to 3.5.0 by
@dependabot[bot] in apache/parquet-java#3092- Bump commons-logging:commons-logging from 1.3.3 to 1.3.4 by
@dependabot[bot] in apache/parquet-java#3094- Bump net.openhft:zero-allocation-hashing from 0.26ea0 to 0.27ea0 by
@dependabot[bot] in apache/parquet-java#3093- Bump com.google.api.grpc:proto-google-common-protos from 2.41.0 to 2.50.0 by
@dependabot[bot] in apache/parquet-java#3109- Bump jackson.version from 2.18.1 to 2.18.2 by
@dependabot[bot] in apache/parquet-java#3108- MINOR: Remove
scalaproperties frompom.xmlby@Fokkoin apache/parquet-java#3104- GH-3114: Fix LogicalType conversions for nested records on Avro <= 1.8 by
@clairemcgintyin apache/parquet-java#3111- Bump com.google.truth.extensions:truth-proto-extension from 1.4.3 to 1.4.4 by
@dependabot[bot] in apache/parquet-java#3107- Bump org.cyclonedx:cyclonedx-maven-plugin from 2.8.0 to 2.9.1 by
@dependabot[bot] in apache/parquet-java#3120- Bump org.apache.commons:commons-text from 1.12.0 to 1.13.0 by
@dependabot[bot] in apache/parquet-java#3119- MINOR: Remove Joda as a direct dependency by
@Fokkoin apache/parquet-java#3132- Bump org.easymock:easymock from 5.4.0 to 5.5.0 by
@dependabot[bot] in apache/parquet-java#3131- GH-3099 add libthrift to parquet-cli shaded jar by
@Arnaud-Nauwynckin apache/parquet-java#3100- GH-3127: Enabled
parquet.hadoop.vectored.io.enabledby default by@dongjoon-hyunin apache/parquet-java#3128- GH-3123: Omit level histogram for some max levels by
@wgtmacin apache/parquet-java#3124- GH-3133: Fix SizeStatistics to handle omitted histogram by
@wgtmacin apache/parquet-java#3134- GH-3125: Add CLI for SizeStatistics by
@wgtmacin apache/parquet-java#3126- GH-3115-Fix int96 read issue in complex type by
@pratyush-sharma-2025in apache/parquet-java#3118- MINOR: Remove
parquet-toolsfromNOTICEby@Fokkoin apache/parquet-java#3140- Bump com.google.guava:guava from 33.2.1-jre to 33.4.0-jre by
@dependabot[bot] in apache/parquet-java#3137- Bump protobuf.version from 3.25.5 to 3.25.6 by
@dependabot[bot] in apache/parquet-java#3138- MINOR: Improve exception message in InternalFileDecryptor by
@zhongyujiangin apache/parquet-java#3143- Bump com.google.api.grpc:proto-google-common-protos from 2.50.0 to 2.51.0 by
@dependabot[bot] in apache/parquet-java#3151- MINOR: Remove release script by
@Fokkoin apache/parquet-java#3144- Deprecate Apache Pig integration by
@Fokkoin apache/parquet-java#3153- Bump com.h2database:h2 from 2.3.230 to 2.3.232 by
@dependabot[bot] in apache/parquet-java#3158- Bump commons-logging:commons-logging from 1.3.4 to 1.3.5 by
@dependabot[bot] in apache/parquet-java#3159- Add logical type annotation for
UnknownTypeby@Fokkoin apache/parquet-java#3154- GH-3156: Enable vectored IO by default. by
@ahmarsuhailin apache/parquet-java#3155- Bump it.unimi.dsi:fastutil from 8.5.13 to 8.5.15 by
@dependabot[bot] in apache/parquet-java#3162- GH-3122: Correct V2 page header compression fields for zero-size data pages by
@ConeyLiuin apache/parquet-java#3148- GH-3163: Reduce memory and time overhead of ParquetRewriterTests by
@rahulketchin apache/parquet-java#3164- MINOR: Reader fails fast when footer size is larger than INT_MAX by
@ConeyLiuin apache/parquet-java#3136- GH-3168: Restrict trusted packages in the parquet-avro module by
@wgtmacin apache/parquet-java#3169- GH-3172: Do not drop blocks with some null values if
DictionaryFilteris applied forUserDefinedPredicatewhich keeps null values by@ebartkusin apache/parquet-java#3173- Bump jackson.version from 2.18.2 to 2.18.3 by
@dependabot[bot] in apache/parquet-java#3170- MINOR: update latest version to 1.15.1 by
@wgtmacin apache/parquet-java#3179- Bump com.google.api.grpc:proto-google-common-protos from 2.51.0 to 2.54.1 by
@dependabot[bot] in apache/parquet-java#3177- Bump Parquet Format to 2.11 by
@Fokkoin apache/parquet-java#3181- [MINOR] Enable jitpack.io repo only when brotli is required by
@pan3793in apache/parquet-java#3180
... (truncated)
402c381
[maven-release-plugin] prepare release apache-parquet-1.16.0-rc20e279ef
Add comparator for UnknownLogicalType (#3292)
(#3295)f85f083
[maven-release-plugin] prepare for next development iteration36d1880
[maven-release-plugin] prepare release apache-parquet-1.16.0-rc12d463ee
[maven-release-plugin] prepare for next development iteration1e3d701
[maven-release-plugin] prepare release apache-parquet-1.16.0-rc07ef2f91
bump parquet-plugins to 1.16.0 for release0d25e13
MINOR: Bump parquet-format to 2.12.0 (#3285)299b0ae
MINOR: Bump thrift to 0.22.0 (#3229)36a5f9c
Bump jackson.version from 2.19.0 to 2.19.2 (#3266)Sourced from org.immutables:value-annotations's releases.
2.11.7
Maintenance & refinements release
Thank you for the bug reports and suggestions!
Issues
- #1621 Version 2.11.7 tag not present on GitHub
- #1611 Jspecify Nullable doesn't work properly with generics
- #1612 Conflicting constructor on empty interfaces when
allParameters = true, andprivateNoArgConstructor = true/protectedNoArgConstructor = true(edge case regression after #1604)- #1579 TYPE_USE Nullable annotation not respected in the builder for arrays (arrays/elements annotation mirrors are missing) (addressed with some source code parsing, which requires
-sourcepathto be provided during compilation)PRs
- Fix
.unset*()for modifiable with primitive field and default value. by@aquariusrickin immutables/immutables#1606- Remove redundant cast of optional record property wither methods by
@werliin immutables/immutables#1616New Contributors
@aquariusrickmade their first contribution in immutables/immutables#1606@werlimade their first contribution in immutables/immutables#1616Full Changelog: https://github.com/immutables/immutables/compare/2.11.6...2.11.7
2.11.6
Maintenance & refinements release
Thank you for the bug reports and suggestions!
Issues
- #1602 Avoid calling check/validation method twice when using plain public constructors (
@Style(of = "new")- #1603 Fixed compilation error with staged builders and complex generics
- #1604 parameterless constructor when there's no attributes, but
allParameters=trueorallMandatoryParameters=trueFull Changelog: https://github.com/immutables/immutables/compare/2.11.5...2.11.6
2.11.5
Maintenance & refinements release
Thank you for the bug reports and PRs!
Issues
- #1602
@Checkmethods (returningvoidi.e. non-normalizing) now works from plain public constructors (@Style(of = "new")- #1583 Staged builder now works for "outside"/top-level class builders, including record builders (with
*BuildStagesclass generated to hold stage interfaces)- #1598 fixed:
@Datafromorg.immutables:datatypecan be used as meta-annotation- #1433
additionalStrictContainerConstructor=falsecan be used to suppress redundant strict factory method (constructor) overloadPRs
- #1600 Stop infinite recursion for annotations applying to themselves by
@aldexisin immutables/immutables#1601New Contributors
@aldexismade their first contribution in immutables/immutables#1601
... (truncated)
2148f28
v2.11.7e65fa80
#1614
investigating "duplicate" nullable annotationsd2ac4f1
Merge pull request #1616
from werli/fix-build-with-optional1470f17
Conditionally remove unnecessary cast for optional record wither
methodsc56f082
#1611
#1579
advancing hacks and workarounds for type_use / nullable annotations95df0cd
Custom nullable in nullableAnnotation should not use
qualified notationf7a662e
#1610
derived arrays, nullable array cloning9001c82
#1612
false negative in testeecbc5b
#1612
fixing and refining no-arg constructorsb659a65
whatever to buildSourced from ch.qos.logback:logback-classic's releases.
Logback 1.5.24
2026-01-06 Release of logback version 1.5.24
• Added ExpressionPropertyCondition a PropertyCondition that can evaluate boolean expressions similar to Java. See the relevant documentation for further details.
• A bit-wise identical binary of this version can be reproduced by building from source code at commit 62bc5fc245dd3a52f3dd45e232733f4cefb4806d associated with the tag v_1.5.24. Release built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.
Logback 1.5.23
2025-12-21 Release of logback version 1.5.23
• In response to issues/959 file name collisions are detected at configuration time by analyzing the configuration file and no longer at run time. This avoids the
ConcurrentModificationExceptionreported in the issue.• ZIP and XZ compression now use a
BufferedOutputStreamwhen writing to the compressed file. This issue was reported in issues/988.• A bit-wise identical binary of this version can be reproduced by building from source code at commit 0bcc3feb54a6d99caac70969ee5f8334aad1fbaf associated with the tag v_1.5.23. Release built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.
Logback 1.5.22
2025-12-11 Release of logback version 1.5.22
• In order to prevent involuntary information leakage, Logback will no longer output the value of a substituted variable, if the variable name contains any of the case-insensitive strings "password", "secret" or "confidential". This problem was reported by Chintan Rohila in issues/986.
• Logback now takes the overridden
toString()method ofThrowablesubclasses into account when printing stack traces. This issue was reported in LOGBACK-543 by Alvin Chee, with a fix provided in PR 404 by Brett Kail.• Instead of limit-counting guard, Logback now uses a tumbling-window guard to rate limit internal error messages.
• A bit-wise identical binary of this version can be reproduced by building from source code at commit 572379aabd2f672b49593e4020696c624541e5b0 associated with the tag v_1.5.22. Release built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.
62bc5fc
prepare release 1.5.24aac604d
typo fix of local variable name8a6df9e
ExpressionPropertyCondition constructor should be public95e588c
minor changes in ExpressionPropertyCondition859f5a1
added ExpressionPropertyCondition capable of parsing logical expressions
on p...348075a
start work on 1.5.24-SNAPSHOT0bcc3fe
prepare release 1.5.234627dbd
better to use BufferedOutputStream during ZIP and XZ compression,
especially ...299f091
add collision test in presence of conditional processingb446f3f
In Context, remove collision mapSourced from ch.qos.logback:logback-core's releases.
Logback 1.5.24
2026-01-06 Release of logback version 1.5.24
• Added ExpressionPropertyCondition a PropertyCondition that can evaluate boolean expressions similar to Java. See the relevant documentation for further details.
• A bit-wise identical binary of this version can be reproduced by building from source code at commit 62bc5fc245dd3a52f3dd45e232733f4cefb4806d associated with the tag v_1.5.24. Release built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.
Logback 1.5.23
2025-12-21 Release of logback version 1.5.23
• In response to issues/959 file name collisions are detected at configuration time by analyzing the configuration file and no longer at run time. This avoids the
ConcurrentModificationExceptionreported in the issue.• ZIP and XZ compression now use a
BufferedOutputStreamwhen writing to the compressed file. This issue was reported in issues/988.• A bit-wise identical binary of this version can be reproduced by building from source code at commit 0bcc3feb54a6d99caac70969ee5f8334aad1fbaf associated with the tag v_1.5.23. Release built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.
Logback 1.5.22
2025-12-11 Release of logback version 1.5.22
• In order to prevent involuntary information leakage, Logback will no longer output the value of a substituted variable, if the variable name contains any of the case-insensitive strings "password", "secret" or "confidential". This problem was reported by Chintan Rohila in issues/986.
• Logback now takes the overridden
toString()method ofThrowablesubclasses into account when printing stack traces. This issue was reported in LOGBACK-543 by Alvin Chee, with a fix provided in PR 404 by Brett Kail.• Instead of limit-counting guard, Logback now uses a tumbling-window guard to rate limit internal error messages.
• A bit-wise identical binary of this version can be reproduced by building from source code at commit 572379aabd2f672b49593e4020696c624541e5b0 associated with the tag v_1.5.22. Release built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.
62bc5fc
prepare release 1.5.24aac604d
typo fix of local variable name8a6df9e
ExpressionPropertyCondition constructor should be public95e588c
minor changes in ExpressionPropertyCondition859f5a1
added ExpressionPropertyCondition capable of parsing logical expressions
on p...348075a
start work on 1.5.24-SNAPSHOT0bcc3fe
prepare release 1.5.234627dbd
better to use BufferedOutputStream during ZIP and XZ compression,
especially ...299f091
add collision test in presence of conditional processingb446f3f
In Context, remove collision mapSourced from org.codehaus.mojo:exec-maven-plugin's releases.
3.6.3
📝 Documentation updates
- Document thread group isolation limitation in java goal (#503) @copilot-swe-agent[bot]
👻 Maintenance
- JUnit 5 best practices (#505)
@slachiewicz- Move ExecJavaMojoTest, ExecMojoTest to JUnit 5 (#502)
@slawekjaranowski- Add support for JEP 512 for for package-private static main method (#499)
@anuragagarwal561994- Move to JUnit 5 (#501)
@slawekjaranowski📦 Dependency updates
- Bump asm.version from 9.9 to 9.9.1 (#509) @dependabot[bot]
- Bump org.apache.commons:commons-exec from 1.5.0 to 1.6.0 (#508) @dependabot[bot]
3.6.2
🚀 New features and improvements
- Add JPMS ServiceLoader Support with Multi-Release JAR (#500)
@ascheman📦 Dependency updates
- Bump asm.version from 9.8 to 9.9 (#498) @dependabot[bot]
3.6.1
🐛 Bug Fixes
- Revert change from #480 - plugin dependencies must be resolved from plugin repositories (#496)
@slawekjaranowski📦 Dependency updates
- Bump org.codehaus.mojo:mojo-parent from 93 to 94 (#495) @dependabot[bot]
3.6.0
🚀 New features and improvements
- [ExecMojo]Add
getShebangmethod to correctly set the command line executable name (#487)@uchenily- JEP 512 Support (#484)
@cayhorstmann🐛 Bug Fixes
- fix inheritIo option (#488)
@dernasherbrezon- Fix for #479 - Wrong repositories used to collect deps (#480)
@cstamas
... (truncated)
fe1fa8c
[maven-release-plugin] prepare release 3.6.35b3feca
Bump asm.version from 9.9 to 9.9.1efc7faa
Bump org.apache.commons:commons-exec from 1.5.0 to 1.6.0cdaf267
JUnit 5 best practices (#505)f3f5997
Move ExecJavaMojoTest, ExecMojoTest to JUnit 503b87b5
Document thread group isolation limitation in java goal (#503)7a66c3e
Add support for JEP 512 for for package-private static main methods with
and ...a6d01ef
Move to JUnit 588d5961
[maven-release-plugin] prepare for next development iteration416fdf1
[maven-release-plugin] prepare release 3.6.2Sourced from org.apache.commons:commons-text's changelog.
Apache Commons Text 1.15.0 Release Notes
The Apache Commons Text team is pleased to announce the release of Apache Commons Text 1.15.0.
Apache Commons Text is a set of utility functions and reusable components for processing and manipulating text in a Java environment.
Release 1.15.0. This is a feature and maintenance release. Java 8 or later is required.
New features
Add experimental CycloneDX VEX file [#683](https://github.com/apache/commons-text/issues/683). Thanks to Piotr P. Karwasz, Gary Gregory.- TEXT-235: Add Damerau-Levenshtein distance #687. Thanks to LorgeN, Gary Gregory.
Add unit tests to increase coverage [#719](https://github.com/apache/commons-text/issues/719). Thanks to Michael Hausegger, Gary Gregory.Add new test for CharSequenceTranslator#with() [#725](https://github.com/apache/commons-text/issues/725). Thanks to Michael Hausegger, Gary Gregory.Add tests and assertions to org.apache.commons.text.similarity to get to 100% code coverage [#727](https://github.com/apache/commons-text/issues/727), [#728](https://github.com/apache/commons-text/issues/728). Thanks to Michael Hausegger.Fixed Bugs
Fix exception message typo in XmlStringLookup.XmlStringLookup(Map, Path...). Thanks to Gary Gregory.- TEXT-236: Inserting at the end of a TextStringBuilder throws a StringIndexOutOfBoundsException. Thanks to Pierre Post, Sumit Bera, Alex Herbert, Gary Gregory.
Fix TextStringBuilderTest.testAppendToCharBuffer() to use proper argument type [#724](https://github.com/apache/commons-text/issues/724). Thanks to Michael Hausegger.Fix Apache RAT plugin console warnings. Thanks to Gary Gregory.Fix site XML to use version 2.0.0 XML schema. Thanks to Gary Gregory.Removed unreachable threshold verification code in src/main/java/org/apache/commons/text/similarity [#730](https://github.com/apache/commons-text/issues/730). Thanks to Michael Hausegger.Enable secure processing for the XML parser in XmlStringLookup in case the underlying JAXP implementation doesn't [#729](https://github.com/apache/commons-text/issues/729). Thanks to 김민재 (minjas0507), Gary Gregory, Piotr Karwasz.Changes
Bump org.apache.commons:commons-parent from 85 to 93 [#704](https://github.com/apache/commons-text/issues/704), [#723](https://github.com/apache/commons-text/issues/723), [#726](https://github.com/apache/commons-text/issues/726). Thanks to Gary Gregory.Bump commons.bytebuddy.version from 1.17.6 to 1.18.2 [#696](https://github.com/apache/commons-text/issues/696), [#722](https://github.com/apache/commons-text/issues/722). Thanks to Gary Gregory.Bump graalvm.version from 24.2.2 to 25.0.1 [#703](https://github.com/apache/commons-text/issues/703), [#716](https://github.com/apache/commons-text/issues/716). Thanks to Gary Gregory, Dependabot.Bump org.apache.commons:commons-lang3 from 3.18.0 to 3.20.0. Thanks to Gary Gregory.Bump commons-io:commons-io from 2.20.0 to 2.21.0. Thanks to Gary Gregory.Historical list of changes: https://commons.apache.org/proper/commons-text/changes.html
For complete information on Apache Commons Text, including instructions on how to submit bug reports, patches, or suggestions for improvement, see the Apache Commons Text website:
https://commons.apache.org/proper/commons-text
Download page: https://commons.apache.org/proper/commons-text/download_text.cgi
... (truncated)
04e9374
Prepare for the release candidate 1.15.0 RC1502c4c4
Prepare for the next release candidatec6e17ec
Use direct access58e1e12
Simplify XML FSP (#731)b5052c9
Bump actions/setup-java from 5.0.0 to 5.1.02e2d4bc
Revert "Bump actions/setup-java from 5.0.0 to 5.1.0"b0ddbd1
Bump actions/setup-java from 5.0.0 to 5.1.01c2d382
Add tests with external DTDed3df4b
Internal clean upbb508f3
Bump actions/checkout from 6.0.0 to 6.0.1Sourced from io.grpc:grpc-bom's releases.
V1.78.0
Bug Fixes
- core: Fix shutdown failing accepted RPCs during channel startup (02e98a806). This fixes a race where RPCs could fail with "UNAVAILABLE: Channel shutdown invoked" even though they were created before channel.shutdown()
- okhttp: Fix race condition overwriting MAX_CONCURRENT_STREAMS (#12548) (8d49dc1c9)
- binder: Stop leaking
thisfrom BinderServerTransport's ctor (#12453) (89d77e062)- rls: Avoid missed config update from reentrancy (55ae1d054). This fixes a regression since 1.75.0 triggered by CdsLb being converted to XdsDepManager. Without this fix, a second channel to the same target may hang when starting, causing DEADLINE_EXCEEDED, and unhang when the control plane delivers an update (e.g., endpoint address update)
Improvements
- xds: gRFC A88 - Changes to XdsClient Watcher APIs (#12446) (f385add31). We now have improved xDS error handling and this provides a clearer mechanism for the xDS server to report per-resource errors to the client, resulting in better error messages for debugging and faster detection of non-existent resources. This also improves the handling of all xDS-related data errors and the behavior of the xDS resource timer.
- rls: Control plane channel monitor state and back off handling (#12460) (26c1c1341). Resets RLS request backoff timers when the Control plane channel state transitions to READY. Also when the backoff timer expires, instead of making a RLS request immediately, it just causes a picker update to allow making rpc again to the RLS target.
- core: simplify DnsNameResolver.resolveAddresses() (4843256af)
- netty: Run handshakeCompleteRunnable in success cases (283f1031f)
- api,netty: Add custom header support for HTTP CONNECT proxy (bbc0aa369)
- binder: Pre-factor out the guts of the BinderClientTransport handshake. (9313e87df)
- compiler: Add RISC-V 64-bit architecture support to compiler build configuration (725ab22f3)
- core: Release lock before closing shared resource (cb73f217e). Shared resources are internal to gRPC for sharing expensive objects across channels and servers, like threads. This reduces the chances of forming a deadlock, like seen with s2a in d50098f
- Upgrade gson to 2.12.1 (6dab2ceab)
- Upgrade dependencies (f36defa2d). proto-google-common-protos to 2.63.1, google-auth-library to 1.40.0, error-prone annotations to 2.44.0, guava to 33.5.0-android, opentelemetry to 1.56.0
- compiler: Update maximum supported protobuf edition to EDITION_2024 (2f64092b8)
- binder: Introduce server authorization strategy v2 (d9710725d). Adds support for
android:isolatedProcessServices and moves all security checks to the handshake, making subsequent transactions more efficient.New Features
- compiler: Upgrade to C++ protobuf 33.1 (#12534) (58ae5f808).
- util: Add gRFC A68 random subsetting LB (48a42889d). The policy uses the name
random_subsetting_experimental. If it is working for you, tell us so we can gauge marking it stable. While the xDS portions haven’t yet landed, it is possible to use with xDS with JSON-style Structs as supported by gRFC A52- xds: Support for System Root Certs (#12499) (51611bad1). Most service mesh workloads use mTLS, as described in gRFC A29. However, there are cases where it is useful for applications to use normal TLS rather than using certificates for workload identity, such as when a mesh wants to move some workloads behind a reverse proxy. The xDS
CertificateValidationContextmessage (see envoyproxy/envoy#34235) has asystem_root_certsfield. In the gRPC client, if this field is present and theca_certificate_provider_instancefield is unset, system root certificates will be used for validation. This implements gRFC A82.- xds: Support for GCP Authentication Filter (#12499) (51611bad1). In service mesh environments, there are cases where intermediate proxies make it impossible to rely on mTLS for end-to-end authentication. These cases can be addressed instead by the use of service account identity JWT tokens. The xDS GCP Authentication filter provides a mechanism for attaching such JWT tokens as gRPC call credentials on GCP. gRPC already supports a framework for xDS HTTP filters, as described in gRFC A39. This release supports the GCP Authentication filter under this framework as described in gRFC A83.
- xds: Support for xDS-based authority rewriting (#12499) (51611bad1). gRPC supports getting routing configuration from an xDS server, as described in gRFCs A27 and A28. The xDS configuration can configure the client to rewrite the authority header on requests. This functionality can be useful in cases where the server is using the authority header to make decisions about how to process the request, such as when multiple hosts are handled via a reverse proxy. Note that this feature is solely about rewriting the authority header on data plane RPCs; it does not affect the authority used in the TLS handshake.
As mentioned in gRFC A29, there are use-cases for gRPC that prohibit trusting the xDS server to control security-centric configuration. The authority rewriting feature falls under the same umbrella as mTLS configuration. As a result, the authority rewriting feature will only be enabled when the bootstrap config for the xDS server hastrusted_xds_serverin theserver_featuresfield.- xds: xDS based SNI setting and SAN validation (#12378) (0567531). When using xDS credentials make SNI for the Tls handshake to be configured via xDS, rather than use the channel authority as the SNI, and make SAN validation to be able to use the SNI sent when so instructed via xDS. Implements gRFC A101.
Documentation
- api: Document gRFC A18 TCP_USER_TIMEOUT handling for keepalive (da7038782)
- core: Fix AbstractClientStream Javadoc (28a6130e8)
- examples: Document how to preserve META-INF/services in uber jars (97695d523)
Thanks to
... (truncated)
8fa6000
Bump version to 1.78.087df333
Update README etc to reference 1.78.0283f103
netty: Run handshakeCompleteRunnable in success caseseb8a63c
Introduce io.grpc.Uri. (#12535)58ae5f8
compiler: Upgrade to protobuf 33.1 (#12534)8d49dc1
okhttp: Fix race condition overwriting MAX_CONCURRENT_STREAMS (#12548)b1a94a4
xds: implement server feature fail_on_data_errors (#12544)55ae1d0
rls: Avoid missed config update from reentrancy53a0926
xds: fix race in simpleFlowControl (#12547)f36defa
Upgrade dependenciesSourced from com.github.ben-manes.caffeine:caffeine's releases.
3.2.3
- Fixed frequency tracking of weak keys to use the object's identity hash code (#1902)
- Added support for underscores in
CaffeineSpecwhen using numeric literals (#1890)- Improved the external api to no longer lock when querying for the maximum size or weighted size (#1897)
- Added detection and recovery when a custom
CompletableFutureis in an inconsistent state (quarkus#50513)3.2.2
- Fixed characteristics returned by Spliterators (#1883)
3.2.1
5227a98
minor build touchupscc3f37d
reorganize into separate gradle test suites2299add
Allow users to read the maximum size without locking (fixes #1897)6250b38
clarify policy javadoc and add corresponding test cases (fixes #1927)c975fc0
upgrade error-prone static analyzerd8e0a92
allow the project.version to be overridden by external builders0e46d22
detect if the user's future is inconsistent with the results1971428
use the assemble task for a full build without running the test
suites782ac79
use the key reference with the frequency sketch (fixes #1902)e0dd94b
minor build clean upThis factory is used to create writers for UUID extension type vectors.
- *
- * @see UuidWriterImpl
- * @see org.apache.arrow.vector.extension.UuidType
- */
-public class UuidWriterFactory implements ExtensionTypeWriterFactory {
-
- /**
- * Creates a writer implementation for the given extension type vector.
- *
- * @param extensionTypeVector the vector to create a writer for
- * @return a {@link UuidWriterImpl} if the vector is a {@link UuidVector}, null otherwise
- */
- @Override
- public AbstractFieldWriter getWriterImpl(ExtensionTypeVector extensionTypeVector) {
- if (extensionTypeVector instanceof UuidVector) {
- return new UuidWriterImpl((UuidVector) extensionTypeVector);
- }
- return null;
- }
-}
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
index 8a78add11c..ee3c79d5e3 100644
--- 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
@@ -21,6 +21,7 @@
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}.
@@ -56,6 +57,11 @@ public void writeExtension(Object value) {
vector.setValueCount(getPosition() + 1);
}
+ @Override
+ public void writeExtension(Object value, ArrowType type) {
+ writeExtension(value);
+ }
+
@Override
public void write(ExtensionHolder holder) {
if (holder instanceof UuidHolder) {
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
index cd29f930e1..c249c6eda9 100644
--- a/vector/src/main/java/org/apache/arrow/vector/extension/UuidType.java
+++ b/vector/src/main/java/org/apache/arrow/vector/extension/UuidType.java
@@ -20,6 +20,9 @@
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;
@@ -108,4 +111,9 @@ public FieldVector getNewVector(String name, FieldType fieldType, BufferAllocato
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
index e5398d82cf..7fa50ca761 100644
--- a/vector/src/main/java/org/apache/arrow/vector/holders/NullableUuidHolder.java
+++ b/vector/src/main/java/org/apache/arrow/vector/holders/NullableUuidHolder.java
@@ -17,6 +17,8 @@
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.
@@ -32,4 +34,9 @@
public class NullableUuidHolder extends ExtensionHolder {
/** Buffer containing 16-byte UUID data. */
public ArrowBuf buffer;
+
+ @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
index 484e05c24b..8a0a66e435 100644
--- a/vector/src/main/java/org/apache/arrow/vector/holders/UuidHolder.java
+++ b/vector/src/main/java/org/apache/arrow/vector/holders/UuidHolder.java
@@ -17,6 +17,8 @@
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.
@@ -35,4 +37,9 @@ public class UuidHolder extends ExtensionHolder {
public UuidHolder() {
this.isSet = 1;
}
+
+ @Override
+ public ArrowType type() {
+ return UuidType.INSTANCE;
+ }
}
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 d5cbf925b2..759c84651d 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java
@@ -26,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.UuidHolder;
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;
@@ -1021,6 +1027,79 @@ 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();
+ UuidHolder holder = new UuidHolder();
+ uuidReader.read(holder);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ assertEquals(u1, actualUuid);
+ reader.next();
+ uuidReader = reader.reader();
+ uuidReader.read(holder);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ 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, 0);
+ assertEquals(u3, actualUuid);
+ reader.next();
+ uuidReader = reader.reader();
+ uuidReader.read(holder);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ assertEquals(u4, actualUuid);
+ reader.next();
+ uuidReader = reader.reader();
+ assertFalse(uuidReader.isSet(), "third element should be null");
+ }
+ }
+
private void writeIntValues(UnionLargeListWriter writer, int[] values) {
writer.startList();
for (int v : values) {
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 df3a609f53..e96ac3027c 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java
@@ -35,7 +35,6 @@
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.impl.UuidWriterFactory;
import org.apache.arrow.vector.complex.reader.FieldReader;
import org.apache.arrow.vector.complex.writer.BaseWriter.ExtensionWriter;
import org.apache.arrow.vector.extension.UuidType;
@@ -1217,7 +1216,6 @@ public void testListVectorWithExtensionType() throws Exception {
UUID u2 = UUID.randomUUID();
writer.startList();
ExtensionWriter extensionWriter = writer.extension(UuidType.INSTANCE);
- extensionWriter.addExtensionTypeWriterFactory(new UuidWriterFactory());
extensionWriter.writeExtension(u1);
extensionWriter.writeExtension(u2);
writer.endList();
@@ -1245,7 +1243,6 @@ public void testListVectorReaderForExtensionType() throws Exception {
UUID u2 = UUID.randomUUID();
writer.startList();
ExtensionWriter extensionWriter = writer.extension(UuidType.INSTANCE);
- extensionWriter.addExtensionTypeWriterFactory(new UuidWriterFactory());
extensionWriter.writeExtension(u1);
extensionWriter.writeExtension(u2);
writer.endList();
@@ -1279,23 +1276,78 @@ public void testCopyFromForExtensionType() throws Exception {
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();
+ UuidHolder holder = new UuidHolder();
+ uuidReader.read(holder);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ assertEquals(u1, actualUuid);
+ reader.next();
+ uuidReader = reader.reader();
+ uuidReader.read(holder);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ 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.addExtensionTypeWriterFactory(new UuidWriterFactory());
extensionWriter.writeExtension(u1);
extensionWriter.writeExtension(u2);
- extensionWriter.writeNull();
writer.endList();
- writer.setValueCount(1);
+ // 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();
- // copy values from input to output
+ writer.endList();
+ writer.setValueCount(2);
+
+ // Use TransferPair with ExtensionTypeWriterFactory
+ // This tests the new makeTransferPair API with writerFactory parameter
outVector.allocateNew();
- outVector.copyFrom(0, 0, inVector, new UuidWriterFactory());
- outVector.setValueCount(1);
+ TransferPair transferPair = inVector.makeTransferPair(outVector);
+ transferPair.copyValueSafe(0, 0);
+ transferPair.copyValueSafe(1, 1);
+ outVector.setValueCount(2);
+ // Verify first list
UnionListReader reader = outVector.getReader();
- assertTrue(reader.isSet(), "shouldn't be null");
reader.setPosition(0);
+ assertTrue(reader.isSet(), "first list shouldn't be null");
reader.next();
FieldReader uuidReader = reader.reader();
UuidHolder holder = new UuidHolder();
@@ -1307,6 +1359,23 @@ public void testCopyFromForExtensionType() throws Exception {
uuidReader.read(holder);
actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
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, 0);
+ assertEquals(u3, actualUuid);
+ reader.next();
+ uuidReader = reader.reader();
+ uuidReader.read(holder);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ assertEquals(u4, actualUuid);
+ reader.next();
+ uuidReader = reader.reader();
+ assertFalse(uuidReader.isSet(), "third element should be null");
}
}
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 d9d2ca50dc..bfac1237a4 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestMapVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestMapVector.java
@@ -35,7 +35,6 @@
import org.apache.arrow.vector.complex.StructVector;
import org.apache.arrow.vector.complex.impl.UnionMapReader;
import org.apache.arrow.vector.complex.impl.UnionMapWriter;
-import org.apache.arrow.vector.complex.impl.UuidWriterFactory;
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;
@@ -1285,14 +1284,12 @@ public void testMapVectorWithExtensionType() throws Exception {
writer.startEntry();
writer.key().bigInt().writeBigInt(0);
ExtensionWriter extensionWriter = writer.value().extension(UuidType.INSTANCE);
- extensionWriter.addExtensionTypeWriterFactory(new UuidWriterFactory());
- extensionWriter.writeExtension(u1);
+ extensionWriter.writeExtension(u1, UuidType.INSTANCE);
writer.endEntry();
writer.startEntry();
writer.key().bigInt().writeBigInt(1);
extensionWriter = writer.value().extension(UuidType.INSTANCE);
- extensionWriter.addExtensionTypeWriterFactory(new UuidWriterFactory());
- extensionWriter.writeExtension(u2);
+ extensionWriter.writeExtension(u2, UuidType.INSTANCE);
writer.endEntry();
writer.endMap();
@@ -1327,20 +1324,17 @@ public void testCopyFromForExtensionType() throws Exception {
writer.startEntry();
writer.key().bigInt().writeBigInt(0);
ExtensionWriter extensionWriter = writer.value().extension(UuidType.INSTANCE);
- extensionWriter.addExtensionTypeWriterFactory(new UuidWriterFactory());
- extensionWriter.writeExtension(u1);
+ extensionWriter.writeExtension(u1, UuidType.INSTANCE);
writer.endEntry();
writer.startEntry();
writer.key().bigInt().writeBigInt(1);
- extensionWriter = writer.value().extension(UuidType.INSTANCE);
- extensionWriter.addExtensionTypeWriterFactory(new UuidWriterFactory());
- extensionWriter.writeExtension(u2);
+ extensionWriter.writeExtension(u2, UuidType.INSTANCE);
writer.endEntry();
writer.endMap();
writer.setValueCount(1);
outVector.allocateNew();
- outVector.copyFrom(0, 0, inVector, new UuidWriterFactory());
+ outVector.copyFrom(0, 0, inVector);
outVector.setValueCount(1);
UnionMapReader mapReader = outVector.getReader();
@@ -1576,4 +1570,103 @@ public void testFixedSizeBinaryFirstInitialization() {
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();
+ UuidHolder keyHolder = new UuidHolder();
+ keyReader.read(keyHolder);
+ UUID actualKey = UuidUtility.uuidFromArrowBuf(keyHolder.buffer, 0);
+ 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, 0);
+ 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/TestStructVector.java b/vector/src/test/java/org/apache/arrow/vector/TestStructVector.java
index 21ebeebc86..8c8a45f588 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestStructVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestStructVector.java
@@ -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 Sourced from org.bouncycastle:bcpkix-jdk18on's
changelog. 2.1.1 Version
Release: 1.84
Date: TBD 2.2.1 Version
Release: 1.83
Date: 2025, November 27th. ... (truncated) Sourced from org.apache.parquet:parquet-avro's
releases. ... (truncated) Sourced from org.apache.parquet:parquet-hadoop's
releases. ... (truncated) Sourced from commons-io:commons-io's
changelog. Apache Commons IO 2.21.0 Release Notes The Apache Commons IO team is pleased to announce the release of
Apache Commons IO 2.21.0. The Apache Commons IO library contains utility classes, stream
implementations, file filters,
file comparators, endian transformation classes, and much more. Version 2.21.0: Java 8 or later is required. o FileUtils#byteCountToDisplaySize() supports Zettabyte, Yottabyte,
Ronnabyte and Quettabyte #763.
Thanks to strangelookingnerd, Gary Gregory.
o Add org.apache.commons.io.FileUtils.ONE_RB #763.
Thanks to strangelookingnerd, Gary Gregory.
o Add org.apache.commons.io.FileUtils.ONE_QB #763.
Thanks to strangelookingnerd, Gary Gregory.
o Add org.apache.commons.io.output.ProxyOutputStream.writeRepeat(byte[],
int, int, long). Thanks to Gary Gregory.
o Add org.apache.commons.io.output.ProxyOutputStream.writeRepeat(byte[],
long). Thanks to Gary Gregory.
o Add org.apache.commons.io.output.ProxyOutputStream.writeRepeat(int,
long). Thanks to Gary Gregory.
o Add length unit support in FileSystem limits. Thanks to Piotr P.
Karwasz.
o Add IOUtils.toByteArray(InputStream, int, int) for safer chunked
reading with size validation. Thanks to Piotr P. Karwasz.
o Add org.apache.commons.io.file.PathUtils.getPath(String, String).
Thanks to Gary Gregory.
o Add org.apache.commons.io.channels.ByteArraySeekableByteChannel.
Thanks to Gary Gregory.
o Add IOIterable.asIterable(). Thanks to Gary Gregory.
o Add NIO channel support to o When testing on Java 21 and up, enable
-XX:+EnableDynamicAgentLoading. Thanks to Gary Gregory.
o When testing on Java 24 and up, don't fail FileUtilsListFilesTest for
a different behavior in the JRE. Thanks to Gary Gregory.
o ValidatingObjectInputStream does not validate dynamic proxy
interfaces. Thanks to Stanislav Fort, Gary Gregory.
o BoundedInputStream.getRemaining() now reports Long.MAX_VALUE instead
of 0 when no limit is set. Thanks to Piotr P. Karwasz.
o BoundedInputStream.available() correctly accounts for the maximum read
limit. Thanks to Piotr P. Karwasz.
o Deprecate IOUtils.readFully(InputStream, int) in favor of
toByteArray(InputStream, int). Thanks to Gary Gregory, Piotr P. Karwasz.
o IOUtils.toByteArray(InputStream) now throws IOException on byte array
overflow. Thanks to Piotr P. Karwasz.
o Javadoc general improvements. Thanks to Gary Gregory, Piotr P.
Karwasz.
o IOUtils.toByteArray() now throws EOFException when not enough data is
available #796.
Thanks to Piotr P. Karwasz.
o Fix IOUtils.skip() usage in concurrent scenarios. Thanks to Piotr P.
Karwasz.
o [javadoc] Fix XmlStreamReader Javadoc to indicate the correct class
that is built #806.
Thanks to J Hawkins. o Bump org.apache.commons:commons-parent from 85 to 91 #774,
#783,
#808.
Thanks to Gary Gregory, Dependabot. ... (truncated) 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/UuidReaderImpl.java b/vector/src/main/java/org/apache/arrow/vector/complex/impl/UuidReaderImpl.java
index bb35b960d3..bb7ae13e5b 100644
--- 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
@@ -63,9 +63,7 @@ public boolean isSet() {
@Override
public void read(ExtensionHolder holder) {
- if (holder instanceof UuidHolder) {
- vector.get(idx(), (UuidHolder) holder);
- } else if (holder instanceof NullableUuidHolder) {
+ if (holder instanceof NullableUuidHolder) {
vector.get(idx(), (NullableUuidHolder) holder);
} else {
throw new IllegalArgumentException(
@@ -75,9 +73,7 @@ public void read(ExtensionHolder holder) {
@Override
public void read(int arrayIndex, ExtensionHolder holder) {
- if (holder instanceof UuidHolder) {
- vector.get(arrayIndex, (UuidHolder) holder);
- } else if (holder instanceof NullableUuidHolder) {
+ if (holder instanceof NullableUuidHolder) {
vector.get(arrayIndex, (NullableUuidHolder) holder);
} else {
throw new IllegalArgumentException(
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
index ee3c79d5e3..944b7e2e62 100644
--- 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
@@ -51,8 +51,15 @@ public void writeExtension(Object value) {
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());
+ 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);
}
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
index 7fa50ca761..6a2b4ff604 100644
--- a/vector/src/main/java/org/apache/arrow/vector/holders/NullableUuidHolder.java
+++ b/vector/src/main/java/org/apache/arrow/vector/holders/NullableUuidHolder.java
@@ -35,6 +35,9 @@ 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
index 8a0a66e435..9ec0305f30 100644
--- a/vector/src/main/java/org/apache/arrow/vector/holders/UuidHolder.java
+++ b/vector/src/main/java/org/apache/arrow/vector/holders/UuidHolder.java
@@ -33,6 +33,9 @@ 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;
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 759c84651d..ccc0d3e176 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java
@@ -37,7 +37,7 @@
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.UuidHolder;
+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;
@@ -1071,14 +1071,14 @@ public void testCopyValueSafeForExtensionType() throws Exception {
assertTrue(reader.isSet(), "first list shouldn't be null");
reader.next();
FieldReader uuidReader = reader.reader();
- UuidHolder holder = new UuidHolder();
+ NullableUuidHolder holder = new NullableUuidHolder();
uuidReader.read(holder);
- UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u1, actualUuid);
reader.next();
uuidReader = reader.reader();
uuidReader.read(holder);
- actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u2, actualUuid);
// Verify second list
@@ -1087,12 +1087,12 @@ public void testCopyValueSafeForExtensionType() throws Exception {
reader.next();
uuidReader = reader.reader();
uuidReader.read(holder);
- actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u3, actualUuid);
reader.next();
uuidReader = reader.reader();
uuidReader.read(holder);
- actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u4, actualUuid);
reader.next();
uuidReader = reader.reader();
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 e96ac3027c..1fe4c59f63 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java
@@ -40,8 +40,8 @@
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.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;
@@ -1254,14 +1254,14 @@ public void testListVectorReaderForExtensionType() throws Exception {
reader.setPosition(0);
reader.next();
FieldReader uuidReader = reader.reader();
- UuidHolder holder = new UuidHolder();
+ NullableUuidHolder holder = new NullableUuidHolder();
uuidReader.read(holder);
- UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u1, actualUuid);
reader.next();
uuidReader = reader.reader();
uuidReader.read(holder);
- actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u2, actualUuid);
}
}
@@ -1294,14 +1294,14 @@ public void testCopyFromForExtensionType() throws Exception {
reader.setPosition(0);
reader.next();
FieldReader uuidReader = reader.reader();
- UuidHolder holder = new UuidHolder();
+ NullableUuidHolder holder = new NullableUuidHolder();
uuidReader.read(holder);
- UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u1, actualUuid);
reader.next();
uuidReader = reader.reader();
uuidReader.read(holder);
- actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u2, actualUuid);
}
}
@@ -1350,14 +1350,14 @@ public void testCopyValueSafeForExtensionType() throws Exception {
assertTrue(reader.isSet(), "first list shouldn't be null");
reader.next();
FieldReader uuidReader = reader.reader();
- UuidHolder holder = new UuidHolder();
+ NullableUuidHolder holder = new NullableUuidHolder();
uuidReader.read(holder);
- UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u1, actualUuid);
reader.next();
uuidReader = reader.reader();
uuidReader.read(holder);
- actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u2, actualUuid);
// Verify second list
@@ -1366,12 +1366,12 @@ public void testCopyValueSafeForExtensionType() throws Exception {
reader.next();
uuidReader = reader.reader();
uuidReader.read(holder);
- actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u3, actualUuid);
reader.next();
uuidReader = reader.reader();
uuidReader.read(holder);
- actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u4, actualUuid);
reader.next();
uuidReader = reader.reader();
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 bfac1237a4..274d2973bd 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestMapVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestMapVector.java
@@ -42,7 +42,7 @@
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.UuidHolder;
+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;
@@ -1299,14 +1299,14 @@ public void testMapVectorWithExtensionType() throws Exception {
mapReader.setPosition(0);
mapReader.next();
FieldReader uuidReader = mapReader.value();
- UuidHolder holder = new UuidHolder();
+ NullableUuidHolder holder = new NullableUuidHolder();
uuidReader.read(holder);
- UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u1, actualUuid);
mapReader.next();
uuidReader = mapReader.value();
uuidReader.read(holder);
- actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u2, actualUuid);
}
}
@@ -1341,14 +1341,14 @@ public void testCopyFromForExtensionType() throws Exception {
mapReader.setPosition(0);
mapReader.next();
FieldReader uuidReader = mapReader.value();
- UuidHolder holder = new UuidHolder();
+ NullableUuidHolder holder = new NullableUuidHolder();
uuidReader.read(holder);
- UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u1, actualUuid);
mapReader.next();
uuidReader = mapReader.value();
uuidReader.read(holder);
- actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(u2, actualUuid);
}
}
@@ -1626,9 +1626,9 @@ public void testMapWithUuidKeyAndListUuidValue() throws Exception {
// Read first entry
mapReader.next();
FieldReader keyReader = mapReader.key();
- UuidHolder keyHolder = new UuidHolder();
+ NullableUuidHolder keyHolder = new NullableUuidHolder();
keyReader.read(keyHolder);
- UUID actualKey = UuidUtility.uuidFromArrowBuf(keyHolder.buffer, 0);
+ UUID actualKey = UuidUtility.uuidFromArrowBuf(keyHolder.buffer, keyHolder.start);
assertEquals(key1, actualKey);
FieldReader valueReader = mapReader.value();
@@ -1648,7 +1648,7 @@ public void testMapWithUuidKeyAndListUuidValue() throws Exception {
mapReader.next();
keyReader = mapReader.key();
keyReader.read(keyHolder);
- actualKey = UuidUtility.uuidFromArrowBuf(keyHolder.buffer, 0);
+ actualKey = UuidUtility.uuidFromArrowBuf(keyHolder.buffer, keyHolder.start);
assertEquals(key2, actualKey);
valueReader = mapReader.value();
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestUuidType.java b/vector/src/test/java/org/apache/arrow/vector/TestUuidType.java
index acf9dd6868..99045d1cba 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestUuidType.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestUuidType.java
@@ -233,7 +233,8 @@ void testVectorByteArrayOperations() {
// Verify the bytes match
byte[] actualBytes = new byte[UuidType.UUID_BYTE_WIDTH];
- uuidVector.get(0).getBytes(0, actualBytes);
+ int offset = uuidVector.getStartOffset(0);
+ uuidVector.getDataBuffer().getBytes(offset, actualBytes);
assertArrayEquals(uuidBytes, actualBytes);
}
}
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestUuidVector.java b/vector/src/test/java/org/apache/arrow/vector/TestUuidVector.java
index a3690461cf..b5dd12d89c 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestUuidVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestUuidVector.java
@@ -27,6 +27,7 @@
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;
@@ -136,8 +137,8 @@ void testWriteExtensionWithUnsupportedType() throws Exception {
IllegalArgumentException exception =
assertThrows(IllegalArgumentException.class, () -> writer.writeExtension("invalid-type"));
- assertEquals(
- "Unsupported value type for UUID: class java.lang.String", exception.getMessage());
+ assertTrue(
+ exception.getMessage().contains("Unsupported value type for UUID: java.lang.String"));
}
}
@@ -235,9 +236,9 @@ void testReaderCopyAsValueExtensionVector() throws Exception {
UuidReaderImpl reader = (UuidReaderImpl) vectorForRead.getReader();
reader.copyAsValue(writer);
UuidReaderImpl reader2 = (UuidReaderImpl) vector.getReader();
- UuidHolder holder = new UuidHolder();
+ NullableUuidHolder holder = new NullableUuidHolder();
reader2.read(0, holder);
- UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(uuid, actualUuid);
}
}
@@ -252,10 +253,10 @@ void testReaderReadWithUuidHolder() throws Exception {
UuidReaderImpl reader = (UuidReaderImpl) vector.getReader();
reader.setPosition(0);
- UuidHolder holder = new UuidHolder();
+ NullableUuidHolder holder = new NullableUuidHolder();
reader.read(holder);
- UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(uuid, actualUuid);
assertEquals(1, holder.isSet);
}
@@ -274,7 +275,7 @@ void testReaderReadWithNullableUuidHolder() throws Exception {
NullableUuidHolder holder = new NullableUuidHolder();
reader.read(holder);
- UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(uuid, actualUuid);
assertEquals(1, holder.isSet);
}
@@ -310,10 +311,10 @@ void testReaderReadWithArrayIndexUuidHolder() throws Exception {
UuidReaderImpl reader = (UuidReaderImpl) vector.getReader();
- UuidHolder holder = new UuidHolder();
+ NullableUuidHolder holder = new NullableUuidHolder();
reader.read(1, holder);
- UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, 0);
+ UUID actualUuid = UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start);
assertEquals(uuid2, actualUuid);
assertEquals(1, holder.isSet);
}
@@ -334,7 +335,7 @@ void testReaderReadWithArrayIndexNullableUuidHolder() throws Exception {
NullableUuidHolder holder1 = new NullableUuidHolder();
reader.read(0, holder1);
- assertEquals(uuid1, UuidUtility.uuidFromArrowBuf(holder1.buffer, 0));
+ assertEquals(uuid1, UuidUtility.uuidFromArrowBuf(holder1.buffer, holder1.start));
assertEquals(1, holder1.isSet);
NullableUuidHolder holder2 = new NullableUuidHolder();
@@ -343,7 +344,7 @@ void testReaderReadWithArrayIndexNullableUuidHolder() throws Exception {
NullableUuidHolder holder3 = new NullableUuidHolder();
reader.read(2, holder3);
- assertEquals(uuid2, UuidUtility.uuidFromArrowBuf(holder3.buffer, 0));
+ assertEquals(uuid2, UuidUtility.uuidFromArrowBuf(holder3.buffer, holder3.start));
assertEquals(1, holder3.isSet);
}
}
@@ -374,31 +375,6 @@ public ArrowType type() {
}
}
- @Test
- void testReaderReadWithArrayIndexUnsupportedHolder() 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();
-
- // Create a mock unsupported holder
- ExtensionHolder unsupportedHolder =
- new ExtensionHolder() {
- @Override
- public ArrowType type() {
- return null;
- }
- };
-
- IllegalArgumentException exception =
- assertThrows(IllegalArgumentException.class, () -> reader.read(0, unsupportedHolder));
-
- assertTrue(exception.getMessage().contains("Unsupported holder type for UuidReader"));
- }
- }
-
@Test
void testReaderIsSet() throws Exception {
try (UuidVector vector = new UuidVector("test", allocator)) {
@@ -461,4 +437,290 @@ void testReaderGetField() throws Exception {
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/complex/writer/TestComplexWriter.java b/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java
index b131bf07e2..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
@@ -1169,7 +1169,7 @@ public void simpleUnion() throws Exception {
} else if (i % 5 == 4) {
NullableUuidHolder holder = new NullableUuidHolder();
unionReader.read(holder);
- assertEquals(UuidUtility.uuidFromArrowBuf(holder.buffer, 0), uuid);
+ assertEquals(UuidUtility.uuidFromArrowBuf(holder.buffer, holder.start), uuid);
} else {
assertEquals((float) i, unionReader.readFloat(), 1e-12);
}
@@ -2536,7 +2536,7 @@ public void extensionWriterReader() throws Exception {
{
FieldReader uuidReader = rootReader.reader("uuid1");
uuidReader.setPosition(0);
- UuidHolder uuidHolder = new UuidHolder();
+ NullableUuidHolder uuidHolder = new NullableUuidHolder();
uuidReader.read(uuidHolder);
UUID actualUuid = UuidUtility.uuidFromArrowBuf(uuidHolder.buffer, 0);
assertEquals(u1, actualUuid);
From a1d83179cf6d3cce4660f6f0bf8e7f75867e87bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A9lder=20Greg=C3=B3rio?= This accessor provides JDBC-compatible access to UUID values stored in Arrow's canonical UUID
+ * extension type ('arrow.uuid'). It follows PostgreSQL JDBC driver conventions:
+ *
+ * Handles conversion of UUID values from JDBC parameters to Arrow's UUID extension type. Accepts
+ * both {@link UUID} objects and String representations of UUIDs.
+ */
+public class UuidAvaticaParameterConverter implements AvaticaParameterConverter {
+
+ public UuidAvaticaParameterConverter() {}
+
+ @Override
+ public boolean bindParameter(FieldVector vector, TypedValue typedValue, int index) {
+ if (!(vector instanceof UuidVector)) {
+ return false;
+ }
+
+ UuidVector uuidVector = (UuidVector) vector;
+ Object value = typedValue.toJdbc(null);
+
+ if (value == null) {
+ uuidVector.setNull(index);
+ return true;
+ }
+
+ UUID uuid;
+ if (value instanceof UUID) {
+ uuid = (UUID) value;
+ } else if (value instanceof String) {
+ uuid = UUID.fromString((String) value);
+ } else if (value instanceof byte[]) {
+ byte[] bytes = (byte[]) value;
+ if (bytes.length != 16) {
+ throw new IllegalArgumentException("UUID byte array must be 16 bytes, got " + bytes.length);
+ }
+ uuid = uuidFromBytes(bytes);
+ } else if (value instanceof ByteString) {
+ byte[] bytes = ((ByteString) value).getBytes();
+ if (bytes.length != 16) {
+ throw new IllegalArgumentException("UUID byte array must be 16 bytes, got " + bytes.length);
+ }
+ uuid = uuidFromBytes(bytes);
+ } else {
+ throw new IllegalArgumentException(
+ "Cannot convert " + value.getClass().getName() + " to UUID");
+ }
+
+ uuidVector.setSafe(index, UuidUtility.getBytesFromUUID(uuid));
+ return true;
+ }
+
+ @Override
+ public AvaticaParameter createParameter(Field field) {
+ final String name = field.getName();
+ final int jdbcType = getSqlTypeIdFromArrowType(field.getType());
+ final String typeName = getSqlTypeNameFromArrowType(field.getType());
+ final String className = UUID.class.getCanonicalName();
+ return new AvaticaParameter(false, 0, 0, jdbcType, typeName, className, name);
+ }
+
+ private static UUID uuidFromBytes(byte[] bytes) {
+ final long mostSignificantBits;
+ final long leastSignificantBits;
+ ByteBuffer bb = ByteBuffer.wrap(bytes);
+ // Reads the first eight bytes
+ mostSignificantBits = bb.getLong();
+ // Reads the first eight bytes at this buffer's current
+ leastSignificantBits = bb.getLong();
+
+ return new UUID(mostSignificantBits, leastSignificantBits);
+ }
+}
diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/AvaticaParameterBinder.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/AvaticaParameterBinder.java
index 8c98ee4077..8f40d6698e 100644
--- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/AvaticaParameterBinder.java
+++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/AvaticaParameterBinder.java
@@ -41,10 +41,14 @@
import org.apache.arrow.driver.jdbc.converter.impl.UnionAvaticaParameterConverter;
import org.apache.arrow.driver.jdbc.converter.impl.Utf8AvaticaParameterConverter;
import org.apache.arrow.driver.jdbc.converter.impl.Utf8ViewAvaticaParameterConverter;
+import org.apache.arrow.driver.jdbc.converter.impl.UuidAvaticaParameterConverter;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.vector.FieldVector;
import org.apache.arrow.vector.VectorSchemaRoot;
+import org.apache.arrow.vector.extension.UuidType;
import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.ArrowType.ArrowTypeVisitor;
+import org.apache.arrow.vector.types.pojo.ArrowType.ExtensionType;
import org.apache.calcite.avatica.remote.TypedValue;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -290,5 +294,15 @@ public Boolean visit(ArrowType.RunEndEncoded type) {
throw new UnsupportedOperationException(
"No Avatica parameter binder implemented for type " + type);
}
+
+ @Override
+ public Boolean visit(ExtensionType type) {
+ if (type instanceof UuidType) {
+ return new UuidAvaticaParameterConverter().bindParameter(vector, typedValue, index);
+ }
+
+ // fallback to default implementation
+ return ArrowTypeVisitor.super.visit(type);
+ }
}
}
diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/ConvertUtils.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/ConvertUtils.java
index 5dd4c69c73..dd51ee5361 100644
--- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/ConvertUtils.java
+++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/ConvertUtils.java
@@ -43,8 +43,12 @@
import org.apache.arrow.driver.jdbc.converter.impl.UnionAvaticaParameterConverter;
import org.apache.arrow.driver.jdbc.converter.impl.Utf8AvaticaParameterConverter;
import org.apache.arrow.driver.jdbc.converter.impl.Utf8ViewAvaticaParameterConverter;
+import org.apache.arrow.driver.jdbc.converter.impl.UuidAvaticaParameterConverter;
import org.apache.arrow.flight.sql.FlightSqlColumnMetadata;
+import org.apache.arrow.vector.extension.UuidType;
import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.ArrowType.ArrowTypeVisitor;
+import org.apache.arrow.vector.types.pojo.ArrowType.ExtensionType;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.calcite.avatica.AvaticaParameter;
import org.apache.calcite.avatica.ColumnMetaData;
@@ -294,5 +298,15 @@ public AvaticaParameter visit(ArrowType.RunEndEncoded type) {
throw new UnsupportedOperationException(
"No Avatica parameter binder implemented for type " + type);
}
+
+ @Override
+ public AvaticaParameter visit(ExtensionType type) {
+ if (type instanceof UuidType) {
+ return new UuidAvaticaParameterConverter().createParameter(field);
+ }
+
+ // fallback to default implementation
+ return ArrowTypeVisitor.super.visit(type);
+ }
}
}
diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java
index 5ba3957f8b..7982d5bc73 100644
--- a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java
+++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/utils/SqlTypes.java
@@ -20,11 +20,13 @@
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
+import org.apache.arrow.vector.extension.UuidType;
import org.apache.arrow.vector.types.FloatingPointPrecision;
import org.apache.arrow.vector.types.pojo.ArrowType;
/** SQL Types utility functions. */
public class SqlTypes {
+
private static final Map Verifies that the accessor correctly handles UUID values from Arrow's UUID extension type,
+ * following PostgreSQL JDBC driver conventions.
+ */
+public class ArrowFlightJdbcUuidVectorAccessorTest {
+
+ @RegisterExtension
+ public static RootAllocatorTestExtension rootAllocatorTestExtension =
+ new RootAllocatorTestExtension();
+
+ private static final UUID UUID_1 = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
+ private static final UUID UUID_2 = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
+ private static final UUID UUID_3 = UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479");
+
+ private UuidVector vector;
+ private ArrowFlightJdbcUuidVectorAccessor accessor;
+ private boolean wasNullCalled;
+ private boolean wasNullValue;
+
+ @BeforeEach
+ public void setUp() {
+ vector = rootAllocatorTestExtension.createUuidVector();
+ wasNullCalled = false;
+ wasNullValue = false;
+ ArrowFlightJdbcAccessorFactory.WasNullConsumer wasNullConsumer =
+ (wasNull) -> {
+ wasNullCalled = true;
+ wasNullValue = wasNull;
+ };
+ accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, wasNullConsumer);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ vector.close();
+ }
+
+ @Test
+ public void testGetObjectReturnsUuid() {
+ accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, (wasNull) -> {});
+ Object result = accessor.getObject();
+ assertThat(result, is(UUID_1));
+ assertThat(accessor.wasNull(), is(false));
+ }
+
+ @Test
+ public void testGetObjectReturnsCorrectUuidForEachRow() {
+ accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, (wasNull) -> {});
+ assertThat(accessor.getObject(), is(UUID_1));
+
+ accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 1, (wasNull) -> {});
+ assertThat(accessor.getObject(), is(UUID_2));
+
+ accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 2, (wasNull) -> {});
+ assertThat(accessor.getObject(), is(UUID_3));
+ }
+
+ @Test
+ public void testGetObjectReturnsNullForNullValue() {
+ vector.reset();
+ vector.allocateNew(1);
+ vector.setNull(0);
+ vector.setValueCount(1);
+
+ accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, (wasNull) -> {});
+ Object result = accessor.getObject();
+ assertThat(result, nullValue());
+ assertThat(accessor.wasNull(), is(true));
+ }
+
+ @Test
+ public void testGetObjectClassReturnsUuidClass() {
+ assertThat(accessor.getObjectClass(), equalTo(UUID.class));
+ }
+
+ @Test
+ public void testGetStringReturnsHyphenatedFormat() {
+ accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, (wasNull) -> {});
+ String result = accessor.getString();
+ assertThat(result, is("550e8400-e29b-41d4-a716-446655440000"));
+ assertThat(accessor.wasNull(), is(false));
+ }
+
+ @Test
+ public void testGetStringReturnsNullForNullValue() {
+ vector.reset();
+ vector.allocateNew(1);
+ vector.setNull(0);
+ vector.setValueCount(1);
+
+ accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, (wasNull) -> {});
+ String result = accessor.getString();
+ assertThat(result, nullValue());
+ assertThat(accessor.wasNull(), is(true));
+ }
+
+ @Test
+ public void testGetBytesReturns16ByteArray() {
+ accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, (wasNull) -> {});
+ byte[] result = accessor.getBytes();
+ assertThat(result.length, is(16));
+ assertThat(result, is(UuidUtility.getBytesFromUUID(UUID_1)));
+ assertThat(accessor.wasNull(), is(false));
+ }
+
+ @Test
+ public void testGetBytesReturnsNullForNullValue() {
+ vector.reset();
+ vector.allocateNew(1);
+ vector.setNull(0);
+ vector.setValueCount(1);
+
+ accessor = new ArrowFlightJdbcUuidVectorAccessor(vector, () -> 0, (wasNull) -> {});
+ byte[] result = accessor.getBytes();
+ assertThat(result, nullValue());
+ assertThat(accessor.wasNull(), is(true));
+ }
+
+ @Test
+ public void testWasNullConsumerIsCalled() {
+ accessor =
+ new ArrowFlightJdbcUuidVectorAccessor(
+ vector,
+ () -> 0,
+ (wasNull) -> {
+ wasNullCalled = true;
+ wasNullValue = wasNull;
+ });
+ accessor.getObject();
+ assertThat(wasNullCalled, is(true));
+ assertThat(wasNullValue, is(false));
+ }
+
+ @Test
+ public void testWasNullConsumerIsCalledWithTrueForNull() {
+ vector.reset();
+ vector.allocateNew(1);
+ vector.setNull(0);
+ vector.setValueCount(1);
+
+ accessor =
+ new ArrowFlightJdbcUuidVectorAccessor(
+ vector,
+ () -> 0,
+ (wasNull) -> {
+ wasNullCalled = true;
+ wasNullValue = wasNull;
+ });
+ accessor.getObject();
+ assertThat(wasNullCalled, is(true));
+ assertThat(wasNullValue, is(true));
+ }
+}
diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java
new file mode 100644
index 0000000000..07751f0abc
--- /dev/null
+++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverterTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.driver.jdbc.converter.impl;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.sql.Types;
+import java.util.UUID;
+import org.apache.arrow.driver.jdbc.utils.RootAllocatorTestExtension;
+import org.apache.arrow.vector.UuidVector;
+import org.apache.arrow.vector.extension.UuidType;
+import org.apache.arrow.vector.types.pojo.Field;
+import org.apache.arrow.vector.types.pojo.FieldType;
+import org.apache.arrow.vector.util.UuidUtility;
+import org.apache.calcite.avatica.AvaticaParameter;
+import org.apache.calcite.avatica.ColumnMetaData;
+import org.apache.calcite.avatica.remote.TypedValue;
+import org.apache.calcite.avatica.util.ByteString;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+/**
+ * Tests for {@link UuidAvaticaParameterConverter}.
+ *
+ * Verifies that the converter correctly handles UUID parameter binding from JDBC to Arrow's UUID
+ * extension type.
+ */
+public class UuidAvaticaParameterConverterTest {
+
+ @RegisterExtension
+ public static RootAllocatorTestExtension rootAllocatorTestExtension =
+ new RootAllocatorTestExtension();
+
+ private static final UUID TEST_UUID = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
+
+ private UuidVector vector;
+ private UuidAvaticaParameterConverter converter;
+
+ @BeforeEach
+ public void setUp() {
+ vector = new UuidVector("uuid_param", rootAllocatorTestExtension.getRootAllocator());
+ vector.allocateNew(5);
+ converter = new UuidAvaticaParameterConverter();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ vector.close();
+ }
+
+ @Test
+ public void testBindParameterWithUuidObject() {
+ TypedValue typedValue = TypedValue.ofLocal(ColumnMetaData.Rep.OBJECT, TEST_UUID);
+
+ boolean result = converter.bindParameter(vector, typedValue, 0);
+
+ assertTrue(result);
+ assertThat(vector.getObject(0), is(TEST_UUID));
+ }
+
+ @Test
+ public void testBindParameterWithUuidString() {
+ String uuidString = "550e8400-e29b-41d4-a716-446655440000";
+ TypedValue typedValue = TypedValue.ofLocal(ColumnMetaData.Rep.STRING, uuidString);
+
+ boolean result = converter.bindParameter(vector, typedValue, 0);
+
+ assertTrue(result);
+ assertThat(vector.getObject(0), is(TEST_UUID));
+ }
+
+ @Test
+ public void testBindParameterWithByteArray() {
+ byte[] uuidBytes = UuidUtility.getBytesFromUUID(TEST_UUID);
+ ByteString byteString = new ByteString(uuidBytes);
+ TypedValue typedValue = TypedValue.ofLocal(ColumnMetaData.Rep.BYTE_STRING, byteString);
+
+ boolean result = converter.bindParameter(vector, typedValue, 0);
+
+ assertTrue(result);
+ assertThat(vector.getObject(0), is(TEST_UUID));
+ }
+
+ @Test
+ public void testBindParameterWithNullValue() {
+ TypedValue typedValue = TypedValue.ofLocal(ColumnMetaData.Rep.OBJECT, null);
+
+ boolean result = converter.bindParameter(vector, typedValue, 0);
+
+ assertTrue(result);
+ assertTrue(vector.isNull(0));
+ assertThat(vector.getObject(0), nullValue());
+ }
+
+ @Test
+ public void testBindParameterWithInvalidByteArrayLength() {
+ byte[] invalidBytes = new byte[8]; // Should be 16 bytes
+ ByteString byteString = new ByteString(invalidBytes);
+ TypedValue typedValue = TypedValue.ofLocal(ColumnMetaData.Rep.BYTE_STRING, byteString);
+
+ assertThrows(
+ IllegalArgumentException.class, () -> converter.bindParameter(vector, typedValue, 0));
+ }
+
+ @Test
+ public void testBindParameterWithInvalidType() {
+ TypedValue typedValue = TypedValue.ofLocal(ColumnMetaData.Rep.INTEGER, 12345);
+
+ assertThrows(
+ IllegalArgumentException.class, () -> converter.bindParameter(vector, typedValue, 0));
+ }
+
+ @Test
+ public void testBindParameterMultipleValues() {
+ UUID uuid1 = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
+ UUID uuid2 = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
+ UUID uuid3 = UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479");
+
+ converter.bindParameter(vector, TypedValue.ofLocal(ColumnMetaData.Rep.OBJECT, uuid1), 0);
+ converter.bindParameter(vector, TypedValue.ofLocal(ColumnMetaData.Rep.OBJECT, uuid2), 1);
+ converter.bindParameter(vector, TypedValue.ofLocal(ColumnMetaData.Rep.OBJECT, uuid3), 2);
+
+ assertThat(vector.getObject(0), is(uuid1));
+ assertThat(vector.getObject(1), is(uuid2));
+ assertThat(vector.getObject(2), is(uuid3));
+ }
+
+ @Test
+ public void testCreateParameter() {
+ Field uuidField = new Field("uuid_col", new FieldType(true, UuidType.INSTANCE, null), null);
+
+ AvaticaParameter parameter = converter.createParameter(uuidField);
+
+ assertThat(parameter.name, is("uuid_col"));
+ assertThat(parameter.parameterType, is(Types.OTHER));
+ assertThat(parameter.typeName, is("OTHER"));
+ assertThat(parameter.className, equalTo(UUID.class.getCanonicalName()));
+ }
+}
diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/CoreMockedSqlProducers.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/CoreMockedSqlProducers.java
index 8197d7d95f..7c17755693 100644
--- a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/CoreMockedSqlProducers.java
+++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/CoreMockedSqlProducers.java
@@ -28,8 +28,10 @@
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.IntStream;
import org.apache.arrow.flight.FlightProducer.ServerStreamListener;
@@ -40,10 +42,13 @@
import org.apache.arrow.vector.DateDayVector;
import org.apache.arrow.vector.Float4Vector;
import org.apache.arrow.vector.Float8Vector;
+import org.apache.arrow.vector.IntVector;
import org.apache.arrow.vector.TimeStampMilliVector;
import org.apache.arrow.vector.UInt4Vector;
+import org.apache.arrow.vector.UuidVector;
import org.apache.arrow.vector.VarCharVector;
import org.apache.arrow.vector.VectorSchemaRoot;
+import org.apache.arrow.vector.extension.UuidType;
import org.apache.arrow.vector.types.DateUnit;
import org.apache.arrow.vector.types.FloatingPointPrecision;
import org.apache.arrow.vector.types.TimeUnit;
@@ -52,6 +57,7 @@
import org.apache.arrow.vector.types.pojo.FieldType;
import org.apache.arrow.vector.types.pojo.Schema;
import org.apache.arrow.vector.util.Text;
+import org.apache.arrow.vector.util.UuidUtility;
/** Standard {@link MockFlightSqlProducer} instances for tests. */
// TODO Remove this once all tests are refactor to use only the queries they need.
@@ -62,6 +68,22 @@ public final class CoreMockedSqlProducers {
public static final String LEGACY_CANCELLATION_SQL_CMD = "SELECT * FROM TAKES_FOREVER";
public static final String LEGACY_REGULAR_WITH_EMPTY_SQL_CMD = "SELECT * FROM TEST_EMPTIES";
+ public static final String UUID_SQL_CMD = "SELECT * FROM UUID_TABLE";
+ public static final String UUID_PREPARED_SELECT_SQL_CMD =
+ "SELECT * FROM UUID_TABLE WHERE uuid_col = ?";
+ public static final String UUID_PREPARED_UPDATE_SQL_CMD =
+ "UPDATE UUID_TABLE SET uuid_col = ? WHERE id = ?";
+
+ public static final UUID UUID_1 = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
+ public static final UUID UUID_2 = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
+ public static final UUID UUID_3 = UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479");
+
+ public static final Schema UUID_SCHEMA =
+ new Schema(
+ ImmutableList.of(
+ new Field("id", new FieldType(true, new ArrowType.Int(32, true), null), null),
+ new Field("uuid_col", new FieldType(true, UuidType.INSTANCE, null), null)));
+
private CoreMockedSqlProducers() {
// Prevent instantiation.
}
@@ -78,9 +100,109 @@ public static MockFlightSqlProducer getLegacyProducer() {
addLegacyMetadataSqlCmdSupport(producer);
addLegacyCancellationSqlCmdSupport(producer);
addQueryWithEmbeddedEmptyRoot(producer);
+ addUuidSqlCmdSupport(producer);
+ addUuidPreparedSelectSqlCmdSupport(producer);
+ addUuidPreparedUpdateSqlCmdSupport(producer);
return producer;
}
+ /**
+ * Gets a {@link MockFlightSqlProducer} configured with UUID test data.
+ *
+ * @return a new producer with UUID support.
+ */
+ public static MockFlightSqlProducer getUuidProducer() {
+ final MockFlightSqlProducer producer = new MockFlightSqlProducer();
+ addUuidSqlCmdSupport(producer);
+ return producer;
+ }
+
+ private static void addUuidPreparedUpdateSqlCmdSupport(final MockFlightSqlProducer producer) {
+ final String query = "UPDATE UUID_TABLE SET uuid_col = ? WHERE id = ?";
+ final Schema parameterSchema =
+ new Schema(
+ Arrays.asList(
+ new Field("", new FieldType(true, UuidType.INSTANCE, null), null),
+ Field.nullable("", new ArrowType.Int(32, true))));
+
+ producer.addUpdateQuery(query, 1);
+ producer.addExpectedParameters(
+ UUID_PREPARED_UPDATE_SQL_CMD,
+ parameterSchema,
+ Collections.singletonList(Arrays.asList(CoreMockedSqlProducers.UUID_3, 1)));
+ }
+
+ private static void addUuidPreparedSelectSqlCmdSupport(final MockFlightSqlProducer producer) {
+ final Schema parameterSchema =
+ new Schema(
+ Collections.singletonList(
+ new Field("", new FieldType(true, UuidType.INSTANCE, null), null)));
+
+ final ConsumerChangelog
Commits
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show Commits
f426e00
prepare release of 1.5.25d28931f
restrict object creation to expected supertypeaa264f7
test default variable values in appender-ref ref attribute8fb403a
adjust copyright yearb294a12
check optionList in start()b65040a
Add EpochConverter for milliseconds/seconds since epoch (related to
issue #96...0690174
cla for Duncan Jauncey71dc2af
Removed email address for Tony.1f97ae1
check for undeclared by referenced appendersb07355e
Move the artifact version checking code to VersionUtil in
logback-core.
Updates `ch.qos.logback:logback-core` from 1.5.24 to 1.5.25
Commits
f426e00
prepare release of 1.5.25d28931f
restrict object creation to expected supertypeaa264f7
test default variable values in appender-ref ref attribute8fb403a
adjust copyright yearb294a12
check optionList in start()b65040a
Add EpochConverter for milliseconds/seconds since epoch (related to
issue #96...0690174
cla for Duncan Jauncey71dc2af
Removed email address for Tony.1f97ae1
check for undeclared by referenced appendersb07355e
Move the artifact version checking code to VersionUtil in
logback-core.
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show Commits
901b398
[maven-release-plugin] prepare release jackson-bom-2.21.086a4b9f
...6b5de3a
Prep for 2.21 release3001d78
Merge pull request #116
from FasterXML/tatu/2.21/115-fix-cyclonedx-backport-i...9370292
makeAggregateBom -> makeBom3e4db58
Backport #115
in 2.x for 2.210ce4467
Merge branch '2.20' into 2.x0dc79f5
...6a3d76b
Merge branch '2.20' into 2.x1d52817
Post-release dep version bump
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show Release notes
Apache Parquet 1.17.0
What's Changed
UnknownLogicalType by @Fokko in apache/parquet-java#3292@Fokko in apache/parquet-java#3370@dependabot[bot]
in apache/parquet-java#3366@dependabot[bot]
in apache/parquet-java#3369@pan3793 in apache/parquet-java#3363@dependabot[bot]
in apache/parquet-java#3182@dependabot[bot]
in apache/parquet-java#3373@Fokko in apache/parquet-java#3314@ArnavBalyan in
apache/parquet-java#3340@ArnavBalyan in
apache/parquet-java#3332@ArnavBalyan in
apache/parquet-java#3319@ArnavBalyan
in apache/parquet-java#3272@ArnavBalyan in
apache/parquet-java#3283@ArnavBalyan in
apache/parquet-java#3311@ArnavBalyan in
apache/parquet-java#3306@uwemaurer in apache/parquet-java#3352@joeyutong in apache/parquet-java#3214@ArnavBalyan in
apache/parquet-java#3308@ArnavBalyan in
apache/parquet-java#3268@ArnavBalyan in
apache/parquet-java#3274@ArnavBalyan in
apache/parquet-java#3287@ArnavBalyan in
apache/parquet-java#3288@ArnavBalyan in
apache/parquet-java#3297@ArnavBalyan in
apache/parquet-java#3304@jerolba in apache/parquet-java#3301@ArnavBalyan in
apache/parquet-java#3309@ArnavBalyan in
apache/parquet-java#3313@jerolba in apache/parquet-java#3333@ArnavBalyan in
apache/parquet-java#3335@ArnavBalyan in
apache/parquet-java#3334@ArnavBalyan in
apache/parquet-java#3325@jerolba in apache/parquet-java#3322@ArnavBalyan in
apache/parquet-java#3326@ArnavBalyan in
apache/parquet-java#3330@ArnavBalyan in
apache/parquet-java#3339@Jiayi-Wang-db
in apache/parquet-java#3351@cravani in apache/parquet-java#3359@gszadovszky in
apache/parquet-java#3348@ArnavBalyan in
apache/parquet-java#3293@wgtmac in apache/parquet-java#3305@dossett in apache/parquet-java#3323@dossett in apache/parquet-java#3324@dossett in apache/parquet-java#3329@Fokko in apache/parquet-java#3303New Contributors
@ArnavBalyan
made their first contribution in apache/parquet-java#3288@Jiayi-Wang-db
made their first contribution in apache/parquet-java#3351@joeyutong
made their first contribution in apache/parquet-java#3214Commits
fac0c74
[maven-release-plugin] prepare release apache-parquet-1.17.0-rc0a8ead9d
Bump protobuf.version from 4.33.1 to 4.33.2 (#3373)0ecd799
Allow reading dictionary encoded boolean (#3370)46218f2
Bump commons-io:commons-io from 2.18.0 to 2.21.0 (#3369)7ec3284
Exclude package-info.class from shaded fastutil (#3322)7453be4
Bump com.google.guava:guava from 33.4.0-jre to 33.5.0-jre (#3366)893ef11
Bump easymock 5.6.0 to support Java 25 (#3363)6b2940c
Remove unused parquet-thrift dependencies (#3323)5040a63
Bump protobuf.version from 3.25.6 to 4.30.2 (#3182)2ccc243
MINOR: parquet-avro tests should not debug to stderr (#3329)
Updates `org.apache.parquet:parquet-hadoop` from 1.16.0 to 1.17.0
Release notes
Apache Parquet 1.17.0
What's Changed
UnknownLogicalType by @Fokko in apache/parquet-java#3292@Fokko in apache/parquet-java#3370@dependabot[bot]
in apache/parquet-java#3366@dependabot[bot]
in apache/parquet-java#3369@pan3793 in apache/parquet-java#3363@dependabot[bot]
in apache/parquet-java#3182@dependabot[bot]
in apache/parquet-java#3373@Fokko in apache/parquet-java#3314@ArnavBalyan in
apache/parquet-java#3340@ArnavBalyan in
apache/parquet-java#3332@ArnavBalyan in
apache/parquet-java#3319@ArnavBalyan
in apache/parquet-java#3272@ArnavBalyan in
apache/parquet-java#3283@ArnavBalyan in
apache/parquet-java#3311@ArnavBalyan in
apache/parquet-java#3306@uwemaurer in apache/parquet-java#3352@joeyutong in apache/parquet-java#3214@ArnavBalyan in
apache/parquet-java#3308@ArnavBalyan in
apache/parquet-java#3268@ArnavBalyan in
apache/parquet-java#3274@ArnavBalyan in
apache/parquet-java#3287@ArnavBalyan in
apache/parquet-java#3288@ArnavBalyan in
apache/parquet-java#3297@ArnavBalyan in
apache/parquet-java#3304@jerolba in apache/parquet-java#3301@ArnavBalyan in
apache/parquet-java#3309@ArnavBalyan in
apache/parquet-java#3313@jerolba in apache/parquet-java#3333@ArnavBalyan in
apache/parquet-java#3335@ArnavBalyan in
apache/parquet-java#3334@ArnavBalyan in
apache/parquet-java#3325@jerolba in apache/parquet-java#3322@ArnavBalyan in
apache/parquet-java#3326@ArnavBalyan in
apache/parquet-java#3330@ArnavBalyan in
apache/parquet-java#3339@Jiayi-Wang-db
in apache/parquet-java#3351@cravani in apache/parquet-java#3359@gszadovszky in
apache/parquet-java#3348@ArnavBalyan in
apache/parquet-java#3293@wgtmac in apache/parquet-java#3305@dossett in apache/parquet-java#3323@dossett in apache/parquet-java#3324@dossett in apache/parquet-java#3329@Fokko in apache/parquet-java#3303New Contributors
@ArnavBalyan
made their first contribution in apache/parquet-java#3288@Jiayi-Wang-db
made their first contribution in apache/parquet-java#3351@joeyutong
made their first contribution in apache/parquet-java#3214Commits
fac0c74
[maven-release-plugin] prepare release apache-parquet-1.17.0-rc0a8ead9d
Bump protobuf.version from 4.33.1 to 4.33.2 (#3373)0ecd799
Allow reading dictionary encoded boolean (#3370)46218f2
Bump commons-io:commons-io from 2.18.0 to 2.21.0 (#3369)7ec3284
Exclude package-info.class from shaded fastutil (#3322)7453be4
Bump com.google.guava:guava from 33.4.0-jre to 33.5.0-jre (#3366)893ef11
Bump easymock 5.6.0 to support Java 25 (#3363)6b2940c
Remove unused parquet-thrift dependencies (#3323)5040a63
Bump protobuf.version from 3.25.6 to 4.30.2 (#3182)2ccc243
MINOR: parquet-avro tests should not debug to stderr (#3329)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show Changelog
Introduction
New features
AbstractStreamBuilder. Thanks
to Piotr P. Karwasz.
o Add CloseShieldChannel to close-shielded NIO Channels #786.
Thanks to Piotr P. Karwasz.
o Added IOUtils.checkFromIndexSize as a Java 8 backport of
Objects.checkFromIndexSize #790.
Thanks to Piotr P. Karwasz.Fixed Bugs
Changes
Commits
54073d3
Prepare for the release candidate 2.21.0 RC1f141f09
Prepare for the next release candidateadcf135
Add license header0f499d0
Use new oak logo34a961c
Use HTTPS in URL9e51118
Use HTTPS in URLd715865
Add dependabot email [skip ci]3d6a7e1
Javadocad875d5
Bump actions/upload-artifact from 4.6.2 to 5.0.0 (#810)bc01dee
Bump github/codeql-action from 4.30.9 to 4.31.2 (#811)
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show
+ *
+ */
+public class ArrowFlightJdbcUuidVectorAccessor extends ArrowFlightJdbcAccessor {
+
+ private final UuidVector vector;
+
+ /**
+ * Creates a new accessor for a UUID vector.
+ *
+ * @param vector the UUID vector to access
+ * @param currentRowSupplier supplier for the current row index
+ * @param setCursorWasNull consumer to set the wasNull flag
+ */
+ public ArrowFlightJdbcUuidVectorAccessor(
+ UuidVector vector,
+ IntSupplier currentRowSupplier,
+ ArrowFlightJdbcAccessorFactory.WasNullConsumer setCursorWasNull) {
+ super(currentRowSupplier, setCursorWasNull);
+ this.vector = vector;
+ }
+
+ @Override
+ public Object getObject() {
+ UUID uuid = vector.getObject(getCurrentRow());
+ this.wasNull = uuid == null;
+ this.wasNullConsumer.setWasNull(this.wasNull);
+ return uuid;
+ }
+
+ @Override
+ public Class> getObjectClass() {
+ return UUID.class;
+ }
+
+ @Override
+ public String getString() {
+ UUID uuid = (UUID) getObject();
+ if (uuid == null) {
+ return null;
+ }
+ return uuid.toString();
+ }
+
+ @Override
+ public byte[] getBytes() {
+ UUID uuid = (UUID) getObject();
+ if (uuid == null) {
+ return null;
+ }
+ return UuidUtility.getBytesFromUUID(uuid);
+ }
+}
diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java
new file mode 100644
index 0000000000..b2157890cf
--- /dev/null
+++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/converter/impl/UuidAvaticaParameterConverter.java
@@ -0,0 +1,103 @@
+/*
+ * 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.driver.jdbc.converter.impl;
+
+import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeIdFromArrowType;
+import static org.apache.arrow.driver.jdbc.utils.SqlTypes.getSqlTypeNameFromArrowType;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+import org.apache.arrow.driver.jdbc.converter.AvaticaParameterConverter;
+import org.apache.arrow.vector.FieldVector;
+import org.apache.arrow.vector.UuidVector;
+import org.apache.arrow.vector.types.pojo.Field;
+import org.apache.arrow.vector.util.UuidUtility;
+import org.apache.calcite.avatica.AvaticaParameter;
+import org.apache.calcite.avatica.remote.TypedValue;
+import org.apache.calcite.avatica.util.ByteString;
+
+/**
+ * AvaticaParameterConverter for UUID Arrow extension type.
+ *
+ *
This provider handles service-to-service authentication where no user interaction is required. + * Tokens are cached and automatically refreshed before expiration. + */ +public class ClientCredentialsTokenProvider extends AbstractOAuthTokenProvider { + + /** + * Creates a new ClientCredentialsTokenProvider. + * + * @param tokenUri the OAuth token endpoint URI + * @param clientId the OAuth client ID + * @param clientSecret the OAuth client secret + * @param scope optional OAuth scopes (space-separated) + */ + ClientCredentialsTokenProvider( + URI tokenUri, String clientId, String clientSecret, @Nullable String scope) { + this.tokenUri = Objects.requireNonNull(tokenUri, "tokenUri cannot be null"); + Objects.requireNonNull(clientId, "clientId cannot be null"); + Objects.requireNonNull(clientSecret, "clientSecret cannot be null"); + this.clientAuth = new ClientSecretBasic(new ClientID(clientId), new Secret(clientSecret)); + this.scope = (scope != null && !scope.isEmpty()) ? Scope.parse(scope) : null; + } + + @Override + protected TokenRequest buildTokenRequest() { + return new TokenRequest(tokenUri, clientAuth, new ClientCredentialsGrant(), scope); + } +} diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/client/oauth/OAuthConfiguration.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/client/oauth/OAuthConfiguration.java new file mode 100644 index 0000000000..cba9d4c2e6 --- /dev/null +++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/client/oauth/OAuthConfiguration.java @@ -0,0 +1,240 @@ +/* + * 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.driver.jdbc.client.oauth; + +import com.nimbusds.oauth2.sdk.GrantType; +import java.net.URI; +import java.net.URISyntaxException; +import java.sql.SQLException; +import java.util.Locale; +import java.util.Objects; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** Configuration class for OAuth settings parsed from connection properties. */ +public class OAuthConfiguration { + + private final GrantType grantType; + private final URI tokenUri; + private final @Nullable String clientId; + private final @Nullable String clientSecret; + private final @Nullable String scope; + private final @Nullable String subjectToken; + private final @Nullable String subjectTokenType; + private final @Nullable String actorToken; + private final @Nullable String actorTokenType; + private final @Nullable String audience; + private final @Nullable String resource; + private final @Nullable String requestedTokenType; + + private OAuthConfiguration(Builder builder) throws SQLException { + this.grantType = builder.grantType; + this.tokenUri = builder.tokenUri; + this.clientId = builder.clientId; + this.clientSecret = builder.clientSecret; + this.scope = builder.scope; + this.subjectToken = builder.subjectToken; + this.subjectTokenType = builder.subjectTokenType; + this.actorToken = builder.actorToken; + this.actorTokenType = builder.actorTokenType; + this.audience = builder.audience; + this.resource = builder.resource; + this.requestedTokenType = builder.requestedTokenType; + + validate(); + } + + private void validate() throws SQLException { + Objects.requireNonNull(grantType, "OAuth grant type is required"); + Objects.requireNonNull(tokenUri, "Token URI is required"); + + if (GrantType.CLIENT_CREDENTIALS.equals(grantType)) { + if (clientId == null || clientId.isEmpty()) { + throw new SQLException("clientId is required for client_credentials flow"); + } + if (clientSecret == null || clientSecret.isEmpty()) { + throw new SQLException("clientSecret is required for client_credentials flow"); + } + } else if (GrantType.TOKEN_EXCHANGE.equals(grantType)) { + if (subjectToken == null || subjectToken.isEmpty()) { + throw new SQLException("subjectToken is required for token_exchange flow"); + } + if (subjectTokenType == null || subjectTokenType.isEmpty()) { + throw new SQLException("subjectTokenType is required for token_exchange flow"); + } + } else { + throw new SQLException("Unsupported OAuth grant type: " + grantType); + } + } + + /** + * Creates an OAuthTokenProvider based on the configured grant type. + * + * @return the token provider + * @throws SQLException if the grant type is not supported or configuration is invalid + */ + public OAuthTokenProvider createTokenProvider() throws SQLException { + if (GrantType.CLIENT_CREDENTIALS.equals(grantType)) { + return OAuthTokenProviders.clientCredentials() + .tokenUri(tokenUri) + .clientId(clientId) + .clientSecret(clientSecret) + .scope(scope) + .build(); + } else if (GrantType.TOKEN_EXCHANGE.equals(grantType)) { + OAuthTokenProviders.TokenExchangeBuilder builder = + OAuthTokenProviders.tokenExchange() + .tokenUri(tokenUri) + .subjectToken(subjectToken) + .subjectTokenType(subjectTokenType) + .actorToken(actorToken) + .actorTokenType(actorTokenType) + .audience(audience) + .requestedTokenType(requestedTokenType) + .scope(scope) + .resource(resource); + + if (clientId != null && clientSecret != null) { + builder.clientCredentials(clientId, clientSecret); + } + + return builder.build(); + } else { + throw new SQLException("Unsupported OAuth grant type: " + grantType); + } + } + + /** Builder for OAuthConfiguration. */ + public static class Builder { + private GrantType grantType; + private URI tokenUri; + private @Nullable String clientId; + private @Nullable String clientSecret; + private @Nullable String scope; + private @Nullable String subjectToken; + private @Nullable String subjectTokenType; + private @Nullable String actorToken; + private @Nullable String actorTokenType; + private @Nullable String audience; + private @Nullable String resource; + private @Nullable String requestedTokenType; + + /** + * Sets the OAuth grant type from a string value. + * + *
Accepts either user-friendly names ("client_credentials", "token_exchange") or the full
+ * URN format as defined in RFC 6749 and RFC 8693.
+ *
+ * @param flowStr the flow type string (e.g., "client_credentials", "token_exchange")
+ * @return this builder
+ * @throws SQLException if the flow string is invalid
+ */
+ public Builder flow(String flowStr) throws SQLException {
+ if (flowStr == null || flowStr.isEmpty()) {
+ throw new SQLException("OAuth flow cannot be null or empty");
+ }
+ try {
+ String normalized = flowStr.toLowerCase(Locale.ROOT);
+ // Map user-friendly names to URN format for token_exchange
+ if ("token_exchange".equals(normalized)) {
+ normalized = GrantType.TOKEN_EXCHANGE.getValue();
+ }
+ GrantType parsed = GrantType.parse(normalized);
+ if (!parsed.equals(GrantType.CLIENT_CREDENTIALS)
+ && !parsed.equals(GrantType.TOKEN_EXCHANGE)) {
+ throw new SQLException("Unsupported OAuth flow: " + flowStr);
+ }
+ this.grantType = parsed;
+ } catch (com.nimbusds.oauth2.sdk.ParseException e) {
+ throw new SQLException("Invalid OAuth flow: " + flowStr, e);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the token URI.
+ *
+ * @param tokenUri the OAuth token endpoint URI
+ * @return this builder
+ * @throws SQLException if the URI is invalid
+ */
+ public Builder tokenUri(String tokenUri) throws SQLException {
+ if (tokenUri == null || tokenUri.isEmpty()) {
+ throw new SQLException("Token URI cannot be null or empty");
+ }
+ try {
+ this.tokenUri = new URI(tokenUri);
+ } catch (URISyntaxException e) {
+ throw new SQLException("Invalid token URI: " + tokenUri, e);
+ }
+ return this;
+ }
+
+ public Builder clientId(@Nullable String clientId) {
+ this.clientId = clientId;
+ return this;
+ }
+
+ public Builder clientSecret(@Nullable String clientSecret) {
+ this.clientSecret = clientSecret;
+ return this;
+ }
+
+ public Builder scope(@Nullable String scope) {
+ this.scope = scope;
+ return this;
+ }
+
+ public Builder subjectToken(@Nullable String subjectToken) {
+ this.subjectToken = subjectToken;
+ return this;
+ }
+
+ public Builder subjectTokenType(@Nullable String subjectTokenType) {
+ this.subjectTokenType = subjectTokenType;
+ return this;
+ }
+
+ public Builder actorToken(@Nullable String actorToken) {
+ this.actorToken = actorToken;
+ return this;
+ }
+
+ public Builder actorTokenType(@Nullable String actorTokenType) {
+ this.actorTokenType = actorTokenType;
+ return this;
+ }
+
+ public Builder audience(@Nullable String audience) {
+ this.audience = audience;
+ return this;
+ }
+
+ public Builder resource(@Nullable String resource) {
+ this.resource = resource;
+ return this;
+ }
+
+ public Builder requestedTokenType(@Nullable String requestedTokenType) {
+ this.requestedTokenType = requestedTokenType;
+ return this;
+ }
+
+ public OAuthConfiguration build() throws SQLException {
+ return new OAuthConfiguration(this);
+ }
+ }
+}
diff --git a/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/client/oauth/OAuthCredentialWriter.java b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/client/oauth/OAuthCredentialWriter.java
new file mode 100644
index 0000000000..0d4ad4689f
--- /dev/null
+++ b/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/client/oauth/OAuthCredentialWriter.java
@@ -0,0 +1,42 @@
+/*
+ * 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.driver.jdbc.client.oauth;
+
+import java.sql.SQLException;
+import java.util.Objects;
+import java.util.function.Consumer;
+import org.apache.arrow.flight.CallHeaders;
+import org.apache.arrow.flight.auth2.Auth2Constants;
+
+/** Writes OAuth bearer tokens to Flight call headers. */
+public class OAuthCredentialWriter implements Consumer This class provides a single entry point for creating all OAuth token providers with a
+ * consistent builder API. It supports:
+ *
+ * Example usage:
+ *
+ * This provider exchanges one token for another, commonly used for federated authentication,
+ * delegation, or impersonation scenarios. Tokens are cached and automatically refreshed.
+ */
+public class TokenExchangeTokenProvider extends AbstractOAuthTokenProvider {
+
+ @VisibleForTesting TokenExchangeGrant grant;
+
+ @VisibleForTesting @Nullable List These tests verify that OAuth tokens obtained from an OAuth server are correctly used in
+ * Flight SQL requests.
+ */
+public class OAuthIntegrationTest {
+
+ private static final String VALID_ACCESS_TOKEN = "valid-oauth-access-token-12345";
+ private static final String CLIENT_ID = "test-client-id";
+ private static final String CLIENT_SECRET = "test-client-secret";
+ private static final String SUBJECT_TOKEN = "original-subject-token";
+ private static final String SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt";
+ private static final String TEST_SCOPE = "dremio.all";
+
+ private static final MockFlightSqlProducer FLIGHT_SQL_PRODUCER = new MockFlightSqlProducer();
+
+ @RegisterExtension public static FlightServerTestExtension FLIGHT_SERVER_TEST_EXTENSION;
+
+ static {
+ FLIGHT_SERVER_TEST_EXTENSION =
+ new FlightServerTestExtension.Builder()
+ .authentication(new TokenAuthentication.Builder().token(VALID_ACCESS_TOKEN).build())
+ .producer(FLIGHT_SQL_PRODUCER)
+ .build();
+ }
+
+ @StartStop private final MockWebServer oauthServer = new MockWebServer();
+ private URI tokenEndpoint;
+
+ @BeforeAll
+ public static void setUpClass() {
+ // Register a simple catalog query handler
+ FLIGHT_SQL_PRODUCER.addCatalogQuery(
+ CommandGetCatalogs.getDefaultInstance(),
+ listener -> {
+ try (BufferAllocator allocator = new RootAllocator();
+ VectorSchemaRoot root =
+ VectorSchemaRoot.create(Schemas.GET_CATALOGS_SCHEMA, allocator)) {
+ root.setRowCount(0);
+ listener.start(root);
+ listener.putNext();
+ } catch (Throwable t) {
+ listener.error(t);
+ } finally {
+ listener.completed();
+ }
+ });
+
+ // Register a simple schema query handler for getSchemas()
+ FLIGHT_SQL_PRODUCER.addCatalogQuery(
+ CommandGetDbSchemas.getDefaultInstance(),
+ listener -> {
+ try (BufferAllocator allocator = new RootAllocator();
+ VectorSchemaRoot root =
+ VectorSchemaRoot.create(Schemas.GET_SCHEMAS_SCHEMA, allocator)) {
+ root.setRowCount(0);
+ listener.start(root);
+ listener.putNext();
+ } catch (Throwable t) {
+ listener.error(t);
+ } finally {
+ listener.completed();
+ }
+ });
+ }
+
+ @AfterAll
+ public static void tearDownClass() {
+ AutoCloseables.closeNoChecked(FLIGHT_SQL_PRODUCER);
+ }
+
+ @BeforeEach
+ public void setUp() {
+ tokenEndpoint = oauthServer.url("/oauth/token").uri();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ oauthServer.close();
+ }
+
+ // Helper methods for mock OAuth responses
+
+ private void enqueueSuccessfulTokenResponse() {
+ enqueueSuccessfulTokenResponse(VALID_ACCESS_TOKEN, 3600);
+ }
+
+ private void enqueueSuccessfulTokenResponse(String token, int expiresIn) {
+ String body =
+ String.format(
+ "{\"access_token\":\"%s\",\"token_type\":\"Bearer\",\"expires_in\":%d}",
+ token, expiresIn);
+ oauthServer.enqueue(
+ new MockResponse.Builder()
+ .code(200)
+ .setHeader("Content-Type", "application/json")
+ .body(body)
+ .build());
+ }
+
+ private void enqueueErrorResponse(String error, String description) {
+ String body =
+ String.format("{\"error\":\"%s\",\"error_description\":\"%s\"}", error, description);
+ oauthServer.enqueue(
+ new MockResponse.Builder()
+ .code(400)
+ .setHeader("Content-Type", "application/json")
+ .body(body)
+ .build());
+ }
+
+ private Properties createBaseProperties() {
+ Properties props = new Properties();
+ props.put(ArrowFlightConnectionProperty.HOST.camelName(), "localhost");
+ props.put(
+ ArrowFlightConnectionProperty.PORT.camelName(), FLIGHT_SERVER_TEST_EXTENSION.getPort());
+ props.put(ArrowFlightConnectionProperty.USE_ENCRYPTION.camelName(), false);
+ return props;
+ }
+
+ private String getJdbcUrl() {
+ return String.format(
+ "jdbc:arrow-flight-sql://localhost:%d", FLIGHT_SERVER_TEST_EXTENSION.getPort());
+ }
+
+ // ==================== Client Credentials Flow Tests ====================
+
+ @Test
+ public void testClientCredentialsFlowSuccess() throws Exception {
+ enqueueSuccessfulTokenResponse();
+
+ Properties props = createBaseProperties();
+ props.put(ArrowFlightConnectionProperty.OAUTH_FLOW.camelName(), "client_credentials");
+ props.put(ArrowFlightConnectionProperty.OAUTH_TOKEN_URI.camelName(), tokenEndpoint.toString());
+ props.put(ArrowFlightConnectionProperty.OAUTH_CLIENT_ID.camelName(), CLIENT_ID);
+ props.put(ArrowFlightConnectionProperty.OAUTH_CLIENT_SECRET.camelName(), CLIENT_SECRET);
+ props.put(ArrowFlightConnectionProperty.OAUTH_SCOPE.camelName(), TEST_SCOPE);
+
+ try (Connection conn = DriverManager.getConnection(getJdbcUrl(), props)) {
+ assertFalse(conn.isClosed());
+ // Trigger a Flight call to force OAuth token retrieval
+ conn.getMetaData().getCatalogs().close();
+ }
+
+ // Verify OAuth request was made
+ RecordedRequest oauthRequest = oauthServer.takeRequest(5, TimeUnit.SECONDS);
+ assertNotNull(oauthRequest, "OAuth request should have been made");
+ assertEquals("POST", oauthRequest.getMethod());
+ String body = oauthRequest.getBody().utf8();
+ assertTrue(body.contains("grant_type=client_credentials"));
+ assertTrue(body.contains("scope=" + TEST_SCOPE));
+ }
+
+ @Test
+ public void testClientCredentialsFlowWithUrlParameters() throws Exception {
+ enqueueSuccessfulTokenResponse();
+
+ String url =
+ String.format(
+ "jdbc:arrow-flight-sql://localhost:%d?useEncryption=false"
+ + "&oauth.flow=client_credentials"
+ + "&oauth.tokenUri=%s"
+ + "&oauth.clientId=%s"
+ + "&oauth.clientSecret=%s",
+ FLIGHT_SERVER_TEST_EXTENSION.getPort(),
+ tokenEndpoint.toString(),
+ CLIENT_ID,
+ CLIENT_SECRET);
+
+ try (Connection conn = DriverManager.getConnection(url)) {
+ conn.getMetaData().getCatalogs().close();
+ }
+
+ assertEquals(1, oauthServer.getRequestCount());
+ }
+
+ @Test
+ public void testClientCredentialsFlowInvalidCredentials() throws Exception {
+ enqueueErrorResponse("invalid_client", "Client authentication failed");
+
+ Properties props = createBaseProperties();
+ props.put(ArrowFlightConnectionProperty.OAUTH_FLOW.camelName(), "client_credentials");
+ props.put(ArrowFlightConnectionProperty.OAUTH_TOKEN_URI.camelName(), tokenEndpoint.toString());
+ props.put(ArrowFlightConnectionProperty.OAUTH_CLIENT_ID.camelName(), "wrong-client");
+ props.put(ArrowFlightConnectionProperty.OAUTH_CLIENT_SECRET.camelName(), "wrong-secret");
+
+ Exception ex =
+ assertThrows(
+ Exception.class,
+ () -> {
+ try (Connection conn = DriverManager.getConnection(getJdbcUrl(), props)) {
+ conn.getMetaData().getCatalogs().close();
+ }
+ });
+ // Verify the error message contains the OAuth error somewhere in the exception chain
+ assertTrue(
+ containsInExceptionChain(ex, "invalid_client"),
+ "Exception chain should contain 'invalid_client'");
+ }
+
+ private boolean containsInExceptionChain(Throwable t, String message) {
+ while (t != null) {
+ if (t.getMessage() != null && t.getMessage().contains(message)) {
+ return true;
+ }
+ t = t.getCause();
+ }
+ return false;
+ }
+
+ // ==================== Token Exchange Flow Tests ====================
+
+ @Test
+ public void testTokenExchangeFlowMinimalParameters() throws Exception {
+ enqueueSuccessfulTokenResponse();
+
+ Properties props = createBaseProperties();
+ props.put(ArrowFlightConnectionProperty.OAUTH_FLOW.camelName(), "token_exchange");
+ props.put(ArrowFlightConnectionProperty.OAUTH_TOKEN_URI.camelName(), tokenEndpoint.toString());
+ props.put(
+ ArrowFlightConnectionProperty.OAUTH_EXCHANGE_SUBJECT_TOKEN.camelName(), SUBJECT_TOKEN);
+ props.put(
+ ArrowFlightConnectionProperty.OAUTH_EXCHANGE_SUBJECT_TOKEN_TYPE.camelName(),
+ SUBJECT_TOKEN_TYPE);
+
+ try (Connection conn = DriverManager.getConnection(getJdbcUrl(), props)) {
+ conn.getMetaData().getCatalogs().close();
+ }
+
+ RecordedRequest oauthRequest = oauthServer.takeRequest(5, TimeUnit.SECONDS);
+ assertNotNull(oauthRequest, "OAuth request should have been made");
+ String body = oauthRequest.getBody().utf8();
+ assertTrue(
+ body.contains("grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange"),
+ "Should contain token exchange grant type");
+ assertTrue(body.contains("subject_token=" + SUBJECT_TOKEN));
+ }
+
+ @Test
+ public void testTokenExchangeFlowWithAllParameters() throws Exception {
+ enqueueSuccessfulTokenResponse();
+
+ String actorToken = "actor-token-value";
+ String actorTokenType = "urn:ietf:params:oauth:token-type:access_token";
+ String audience = "https://api.example.com";
+ String resource = "https://api.example.com/resource";
+ String requestedTokenType = "urn:ietf:params:oauth:token-type:access_token";
+
+ Properties props = createBaseProperties();
+ props.put(ArrowFlightConnectionProperty.OAUTH_FLOW.camelName(), "token_exchange");
+ props.put(ArrowFlightConnectionProperty.OAUTH_TOKEN_URI.camelName(), tokenEndpoint.toString());
+ props.put(ArrowFlightConnectionProperty.OAUTH_CLIENT_ID.camelName(), CLIENT_ID);
+ props.put(ArrowFlightConnectionProperty.OAUTH_CLIENT_SECRET.camelName(), CLIENT_SECRET);
+ props.put(ArrowFlightConnectionProperty.OAUTH_SCOPE.camelName(), TEST_SCOPE);
+ props.put(
+ ArrowFlightConnectionProperty.OAUTH_EXCHANGE_SUBJECT_TOKEN.camelName(), SUBJECT_TOKEN);
+ props.put(
+ ArrowFlightConnectionProperty.OAUTH_EXCHANGE_SUBJECT_TOKEN_TYPE.camelName(),
+ SUBJECT_TOKEN_TYPE);
+ props.put(ArrowFlightConnectionProperty.OAUTH_EXCHANGE_ACTOR_TOKEN.camelName(), actorToken);
+ props.put(
+ ArrowFlightConnectionProperty.OAUTH_EXCHANGE_ACTOR_TOKEN_TYPE.camelName(), actorTokenType);
+ props.put(ArrowFlightConnectionProperty.OAUTH_EXCHANGE_AUDIENCE.camelName(), audience);
+ props.put(ArrowFlightConnectionProperty.OAUTH_RESOURCE.camelName(), resource);
+ props.put(
+ ArrowFlightConnectionProperty.OAUTH_EXCHANGE_REQUESTED_TOKEN_TYPE.camelName(),
+ requestedTokenType);
+
+ try (Connection conn = DriverManager.getConnection(getJdbcUrl(), props)) {
+ conn.getMetaData().getCatalogs().close();
+ }
+
+ RecordedRequest oauthRequest = oauthServer.takeRequest(5, TimeUnit.SECONDS);
+ assertNotNull(oauthRequest, "OAuth request should have been made");
+ String body = oauthRequest.getBody().utf8();
+ assertTrue(body.contains("subject_token=" + SUBJECT_TOKEN));
+ assertTrue(body.contains("actor_token=" + actorToken));
+ }
+
+ @Test
+ public void testTokenExchangeFlowWithClientAuthentication() throws Exception {
+ enqueueSuccessfulTokenResponse();
+
+ Properties props = createBaseProperties();
+ props.put(ArrowFlightConnectionProperty.OAUTH_FLOW.camelName(), "token_exchange");
+ props.put(ArrowFlightConnectionProperty.OAUTH_TOKEN_URI.camelName(), tokenEndpoint.toString());
+ props.put(ArrowFlightConnectionProperty.OAUTH_CLIENT_ID.camelName(), CLIENT_ID);
+ props.put(ArrowFlightConnectionProperty.OAUTH_CLIENT_SECRET.camelName(), CLIENT_SECRET);
+ props.put(
+ ArrowFlightConnectionProperty.OAUTH_EXCHANGE_SUBJECT_TOKEN.camelName(), SUBJECT_TOKEN);
+ props.put(
+ ArrowFlightConnectionProperty.OAUTH_EXCHANGE_SUBJECT_TOKEN_TYPE.camelName(),
+ SUBJECT_TOKEN_TYPE);
+
+ try (Connection conn = DriverManager.getConnection(getJdbcUrl(), props)) {
+ conn.getMetaData().getCatalogs().close();
+ }
+
+ RecordedRequest oauthRequest = oauthServer.takeRequest(5, TimeUnit.SECONDS);
+ assertNotNull(oauthRequest, "OAuth request should have been made");
+ String authHeader = oauthRequest.getHeaders().get("Authorization");
+ assertNotNull(authHeader, "Should have Basic auth header for client authentication");
+ assertTrue(authHeader.startsWith("Basic "));
+ }
+
+ // ==================== Token Caching Tests ====================
+
+ @Test
+ public void testTokenCachingAcrossMultipleOperations() throws Exception {
+ enqueueSuccessfulTokenResponse();
+
+ Properties props = createBaseProperties();
+ props.put(ArrowFlightConnectionProperty.OAUTH_FLOW.camelName(), "client_credentials");
+ props.put(ArrowFlightConnectionProperty.OAUTH_TOKEN_URI.camelName(), tokenEndpoint.toString());
+ props.put(ArrowFlightConnectionProperty.OAUTH_CLIENT_ID.camelName(), CLIENT_ID);
+ props.put(ArrowFlightConnectionProperty.OAUTH_CLIENT_SECRET.camelName(), CLIENT_SECRET);
+
+ try (Connection conn = DriverManager.getConnection(getJdbcUrl(), props)) {
+ // Execute multiple operations
+ conn.isValid(5);
+ conn.getMetaData().getCatalogs().close();
+ conn.getMetaData().getSchemas().close();
+ }
+
+ // Should only have made one OAuth request due to caching
+ assertEquals(1, oauthServer.getRequestCount());
+ }
+
+ @Test
+ public void testTokenRefreshAfterExpiration() throws Exception {
+ enqueueSuccessfulTokenResponse(VALID_ACCESS_TOKEN, 1);
+ enqueueSuccessfulTokenResponse(VALID_ACCESS_TOKEN, 3600);
+
+ Properties props = createBaseProperties();
+ props.put(ArrowFlightConnectionProperty.OAUTH_FLOW.camelName(), "token_exchange");
+ props.put(ArrowFlightConnectionProperty.OAUTH_TOKEN_URI.camelName(), tokenEndpoint.toString());
+ props.put(
+ ArrowFlightConnectionProperty.OAUTH_EXCHANGE_SUBJECT_TOKEN.camelName(), SUBJECT_TOKEN);
+ props.put(
+ ArrowFlightConnectionProperty.OAUTH_EXCHANGE_SUBJECT_TOKEN_TYPE.camelName(),
+ SUBJECT_TOKEN_TYPE);
+
+ try (Connection conn = DriverManager.getConnection(getJdbcUrl(), props)) {
+ // First operation triggers initial token fetch
+ conn.getMetaData().getCatalogs().close();
+
+ // Token with 1s expiry is immediately considered expired (due to 30s buffer)
+ // so the next operation should trigger a refresh
+ conn.getMetaData().getCatalogs().close();
+ }
+
+ // Should have made exactly 2 OAuth requests: initial + refresh
+ assertEquals(2, oauthServer.getRequestCount());
+ }
+
+ // ==================== Error Handling Tests ====================
+
+ @Test
+ public void testMissingRequiredParametersClientCredentials() {
+ Properties props = createBaseProperties();
+ props.put(ArrowFlightConnectionProperty.OAUTH_FLOW.camelName(), "client_credentials");
+ props.put(ArrowFlightConnectionProperty.OAUTH_TOKEN_URI.camelName(), tokenEndpoint.toString());
+ // Missing client_id and client_secret
+
+ assertThrows(SQLException.class, () -> DriverManager.getConnection(getJdbcUrl(), props));
+ }
+
+ @Test
+ public void testMissingRequiredParametersTokenExchange() {
+ Properties props = createBaseProperties();
+ props.put(ArrowFlightConnectionProperty.OAUTH_FLOW.camelName(), "token_exchange");
+ props.put(ArrowFlightConnectionProperty.OAUTH_TOKEN_URI.camelName(), tokenEndpoint.toString());
+ // Missing subject_token and subject_token_type
+
+ assertThrows(SQLException.class, () -> DriverManager.getConnection(getJdbcUrl(), props));
+ }
+
+ @Test
+ public void testInvalidOAuthFlow() {
+ Properties props = createBaseProperties();
+ props.put(ArrowFlightConnectionProperty.OAUTH_FLOW.camelName(), "invalid_flow");
+ props.put(ArrowFlightConnectionProperty.OAUTH_TOKEN_URI.camelName(), tokenEndpoint.toString());
+
+ assertThrows(SQLException.class, () -> DriverManager.getConnection(getJdbcUrl(), props));
+ }
+
+ @Test
+ public void testMalformedTokenEndpoint() {
+ Properties props = createBaseProperties();
+ props.put(ArrowFlightConnectionProperty.OAUTH_FLOW.camelName(), "client_credentials");
+ props.put(ArrowFlightConnectionProperty.OAUTH_TOKEN_URI.camelName(), "not-a-valid-uri://");
+ props.put(ArrowFlightConnectionProperty.OAUTH_CLIENT_ID.camelName(), CLIENT_ID);
+ props.put(ArrowFlightConnectionProperty.OAUTH_CLIENT_SECRET.camelName(), CLIENT_SECRET);
+
+ assertThrows(SQLException.class, () -> DriverManager.getConnection(getJdbcUrl(), props));
+ }
+
+ // ==================== Authorization Header Verification ====================
+
+ @Test
+ public void testOAuthTokenSentAsBearer() throws Exception {
+ enqueueSuccessfulTokenResponse();
+
+ Properties props = createBaseProperties();
+ props.put(ArrowFlightConnectionProperty.OAUTH_FLOW.camelName(), "client_credentials");
+ props.put(ArrowFlightConnectionProperty.OAUTH_TOKEN_URI.camelName(), tokenEndpoint.toString());
+ props.put(ArrowFlightConnectionProperty.OAUTH_CLIENT_ID.camelName(), CLIENT_ID);
+ props.put(ArrowFlightConnectionProperty.OAUTH_CLIENT_SECRET.camelName(), CLIENT_SECRET);
+
+ try (Connection conn = DriverManager.getConnection(getJdbcUrl(), props)) {
+ conn.getMetaData().getCatalogs().close();
+ }
+
+ // Verify the Flight server received the bearer token
+ String authHeader =
+ FLIGHT_SERVER_TEST_EXTENSION
+ .getInterceptorFactory()
+ .getHeader(org.apache.arrow.flight.FlightMethod.GET_FLIGHT_INFO, "authorization");
+ assertNotNull(authHeader, "Authorization header should be present in Flight requests");
+ assertEquals("Bearer " + VALID_ACCESS_TOKEN, authHeader);
+ }
+}
diff --git a/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/client/oauth/OAuthConfigurationTest.java b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/client/oauth/OAuthConfigurationTest.java
new file mode 100644
index 0000000000..c258a7c652
--- /dev/null
+++ b/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/client/oauth/OAuthConfigurationTest.java
@@ -0,0 +1,296 @@
+/*
+ * 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.driver.jdbc.client.oauth;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.nimbusds.oauth2.sdk.Scope;
+import java.net.URI;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Named;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/** Tests for {@link OAuthConfiguration}. */
+public class OAuthConfigurationTest {
+
+ private static final String TOKEN_URI = "https://auth.example.com/token";
+ private static final String CLIENT_ID = "test-client-id";
+ private static final String CLIENT_SECRET = "test-client-secret";
+ private static final String SCOPE = "read write";
+ private static final String SUBJECT_TOKEN = "subject-token-value";
+ public static final String RESOURCE = "https://api.example.com/resource";
+
+ @FunctionalInterface
+ interface BuilderConfigurer {
+ void configure(OAuthConfiguration.Builder builder) throws SQLException;
+ }
+
+ static Stream Sourced from org.apache.commons:commons-compress's
changelog. The Apache Commons Compress team is pleased to announce the release
of Apache Commons Compress 1.28.0. Apache Commons Compress defines an API for working with
compression and archive formats. These include bzip2, gzip, pack200,
LZMA, XZ, Snappy, traditional Unix Compress, DEFLATE, DEFLATE64, LZ4,
Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj. This is a feature and maintenance release. Java 8 or later is
required. This release updates Apache Commons Lang to 3.18.0 to pick up the fix
for CVE-2025-48924 (https://nvd.nist.gov/vuln/detail/CVE-2025-48924),
but is not affected by it. Changes in this version include the following. ... (truncated) Sourced from org.assertj:assertj-core's
releases. Thanks to all the contributors who worked on this release: ... (truncated) Sourced from ch.qos.logback:logback-classic's
releases. 2026-01-25 Release of logback version 1.5.26 • InsertFromJNDIModelHandler was accessing javax.naming package
forcing the inclusion of the optional java.naming module. This problem
was raised in issues/1003
by Marius Hanl who also provided the relevant PR. • In applications using shadow/fat/shade jars, module or package
information could be lost. Thus, in the absence of version information,
logback-classic would warn about version mismatches. Logback components
now ship with properties files containing version information that
survive shadow/fat/shade jars. This issue was reporteed in issues/1002
by Christoph Gritschenberger. • A bit-wise identical binary of this version can be reproduced by
building from source code at commit
33deb54506bbfaf1ff151f26f3a5f86936011619 associated with the tag
v_1.5.26. Release built using Java "21" 2023-10-17 LTS build
21.0.1.+12-LTS-29 under Linux Debian 11.6. Sourced from ch.qos.logback:logback-core's
releases. 2026-01-25 Release of logback version 1.5.26 • InsertFromJNDIModelHandler was accessing javax.naming package
forcing the inclusion of the optional java.naming module. This problem
was raised in issues/1003
by Marius Hanl who also provided the relevant PR. • In applications using shadow/fat/shade jars, module or package
information could be lost. Thus, in the absence of version information,
logback-classic would warn about version mismatches. Logback components
now ship with properties files containing version information that
survive shadow/fat/shade jars. This issue was reporteed in issues/1002
by Christoph Gritschenberger. • A bit-wise identical binary of this version can be reproduced by
building from source code at commit
33deb54506bbfaf1ff151f26f3a5f86936011619 associated with the tag
v_1.5.26. Release built using Java "21" 2023-10-17 LTS build
21.0.1.+12-LTS-29 under Linux Debian 11.6. 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 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 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 Sourced from org.mockito:mockito-bom's
releases. Changelog
generated by Shipkit Changelog
Gradle Plugin Changelog
generated by Shipkit Changelog
Gradle Plugin Changelog
generated by Shipkit Changelog
Gradle Plugin ... (truncated)
+ *
+ *
+ * {@code
+ * // Client Credentials flow
+ * OAuthTokenProvider provider = OAuthTokenProviders.clientCredentials()
+ * .tokenUri("https://auth.example.com/token")
+ * .clientId("my-client")
+ * .clientSecret("my-secret")
+ * .scope("read write")
+ * .build();
+ *
+ * // Token Exchange flow
+ * OAuthTokenProvider provider = OAuthTokenProviders.tokenExchange()
+ * .tokenUri("https://auth.example.com/token")
+ * .subjectToken("user-token")
+ * .subjectTokenType("urn:ietf:params:oauth:token-type:access_token")
+ * .build();
+ * }
+ */
+public final class OAuthTokenProviders {
+
+ private OAuthTokenProviders() {}
+
+ /**
+ * Creates a new builder for Client Credentials flow.
+ *
+ * @return a new ClientCredentialsBuilder instance
+ */
+ public static ClientCredentialsBuilder clientCredentials() {
+ return new ClientCredentialsBuilder();
+ }
+
+ /**
+ * Creates a new builder for Token Exchange flow.
+ *
+ * @return a new TokenExchangeBuilder instance
+ */
+ public static TokenExchangeBuilder tokenExchange() {
+ return new TokenExchangeBuilder();
+ }
+
+ /** Builder for creating {@link ClientCredentialsTokenProvider} instances. */
+ public static class ClientCredentialsBuilder {
+ private @Nullable URI tokenUri;
+ private @Nullable String clientId;
+ private @Nullable String clientSecret;
+ private @Nullable String scope;
+
+ ClientCredentialsBuilder() {}
+
+ /**
+ * Sets the OAuth token endpoint URI (required).
+ *
+ * @param tokenUri the token endpoint URI
+ * @return this builder
+ */
+ public ClientCredentialsBuilder tokenUri(URI tokenUri) {
+ this.tokenUri = Objects.requireNonNull(tokenUri, "tokenUri cannot be null");
+ return this;
+ }
+
+ /**
+ * Sets the OAuth token endpoint URI from a string (required).
+ *
+ * @param tokenUri the token endpoint URI string
+ * @return this builder
+ * @throws IllegalArgumentException if the URI is invalid
+ */
+ public ClientCredentialsBuilder tokenUri(String tokenUri) {
+ Objects.requireNonNull(tokenUri, "tokenUri cannot be null");
+ try {
+ this.tokenUri = new URI(tokenUri);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Invalid token URI: " + tokenUri, e);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the OAuth client ID (required).
+ *
+ * @param clientId the client ID
+ * @return this builder
+ */
+ public ClientCredentialsBuilder clientId(String clientId) {
+ this.clientId = Objects.requireNonNull(clientId, "clientId cannot be null");
+ return this;
+ }
+
+ /**
+ * Sets the OAuth client secret (required).
+ *
+ * @param clientSecret the client secret
+ * @return this builder
+ */
+ public ClientCredentialsBuilder clientSecret(String clientSecret) {
+ this.clientSecret = Objects.requireNonNull(clientSecret, "clientSecret cannot be null");
+ return this;
+ }
+
+ /**
+ * Sets the OAuth scopes (optional).
+ *
+ * @param scope the space-separated scope string
+ * @return this builder
+ */
+ public ClientCredentialsBuilder scope(@Nullable String scope) {
+ this.scope = scope;
+ return this;
+ }
+
+ /**
+ * Builds a new ClientCredentialsTokenProvider instance.
+ *
+ * @return the configured ClientCredentialsTokenProvider
+ * @throws IllegalStateException if required parameters are missing
+ */
+ public ClientCredentialsTokenProvider build() {
+ if (tokenUri == null) {
+ throw new IllegalStateException("tokenUri is required");
+ }
+ if (clientId == null) {
+ throw new IllegalStateException("clientId is required");
+ }
+ if (clientSecret == null) {
+ throw new IllegalStateException("clientSecret is required");
+ }
+ return new ClientCredentialsTokenProvider(tokenUri, clientId, clientSecret, scope);
+ }
+ }
+
+ /** Builder for creating {@link TokenExchangeTokenProvider} instances. */
+ public static class TokenExchangeBuilder {
+ private @Nullable URI tokenUri;
+ private @Nullable String subjectToken;
+ private @Nullable String subjectTokenType;
+ private @Nullable String actorToken;
+ private @Nullable String actorTokenType;
+ private @Nullable String audience;
+ private @Nullable String requestedTokenType;
+ private @Nullable Scope scope;
+ private @Nullable ListDependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show Changelog
Apache Commons Compress 1.28.0 Release Notes
Changes in this version
New Features
Add GzipParameters.getModificationInstant(). Thanks to Gary
Gregory.
Add GzipParameters.setModificationInstant(Instant). Thanks
to Gary Gregory.
Add GzipParameters.OS, setOS(OS), getOS(). Thanks to Gary
Gregory.
Add GzipParameters.toString(). Thanks to Gary Gregory.
Add support for gzip extra subfields, see
GzipParameters.setExtra(HeaderExtraField)
[#604](https://github.com/apache/commons-compress/issues/604). Thanks to
ddeschenes-1, Gary Gregory.
Add CompressFilterOutputStream and refactor to use. Thanks
to Gary Gregory.
Add ZipFile.stream(). Thanks to Gary Gregory.
GzipCompressorInputStream reads the modification time
(MTIME) and stores its value incorrectly multiplied by 1,000. Thanks to
Danny Deschenes, Gary Gregory.
GzipCompressorInputStream writes the modification time
(MTIME) the value incorrectly divided by 1,000. Thanks to Danny
Deschenes, Gary Gregory.
Add optional FHCRC to GZIP header
[#627](https://github.com/apache/commons-compress/issues/627). Thanks to
Danny Deschenes, Gary Gregory.
Add GzipCompressorInputStream.Builder allowing to customize
the file name and comment Charsets. Thanks to Gary Gregory.
Add
GzipCompressorInputStream.Builder.setOnMemberStart(IOConsumer) to
monitor member parsing. Thanks to Gary Gregory.
Add
GzipCompressorInputStream.Builder.setOnMemberEnd(IOConsumer) to monitor
member parsing. Thanks to Gary Gregory.
Add PMD check to default Maven goal. Thanks to Gary Gregory.
Add SevenZFile.Builder.setMaxMemoryLimitKiB(int). Thanks to
Gary Gregory.
Add MemoryLimitException.MemoryLimitException(long, int,
Throwable) and deprecate MemoryLimitException.MemoryLimitException(long,
int, Exception). Thanks to Gary Gregory.
Add support for XZ compression in ZIP archives. Thanks to
Gary Gregory.
Add
org.apache.commons.compress.harmony.pack200.Pack200Exception.Pack200Exception(String,
Throwable). Thanks to Gary Gregory.
Add
org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream.builder/Builder().
Thanks to Gary Gregory.
Add
org.apache.commons.compress.compressors.lzma.LZMACompressorOutputStream.builder/Builder().
Thanks to Gary Gregory.
Add
org.apache.commons.compress.compressors.xz.XZCompressorInputStream.builder/Builder().
Thanks to Gary Gregory.
Add
org.apache.commons.compress.compressors.xz.XZCompressorOutputStream.builder/Builder().
Thanks to Gary Gregory.
Add
org.apache.commons.compress.compressors.xz.ZstdCompressorOutputStream.builder/Builder()
[#666](https://github.com/apache/commons-compress/issues/666). Thanks to
Gary Gregory, David Walluck, Piotr P. Karwasz.
Add org.apache.commons.compress.compressors.xz.ZstdConstants
[#666](https://github.com/apache/commons-compress/issues/666). Thanks to
Gary Gregory, David Walluck, Piotr P. Karwasz.
Commits
852d9c2
Prepare for the release candidate 1.28.0 RC1f5eb9e2
Prepare for the next release candidate36f204c
Camel case parameter name4c04e4a
Use final6cb7da1
Javadoc563c9d2
Javadocce73bd8
Javadoca464ae9
Better parameter namesc0b2b84
Add TODO for next major versionc76bc97
Use OpenVEX to document that we are not affected by CVE-2025-48924
in
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show Release notes
v3.27.7
:lock: Security
Core
isXmlEqualTo assertion
(CVE-2026-24400)
@wxt201 and @Song-Li for
responsibly reporting it!:no_entry_sign: Deprecated
Core
XmlStringPrettyFormatter with no
replacement:bug: Bug Fixes
Guava
assertj-core or guava types
from assertj-guava Javadoc site has unnecessary header #3478:hammer: Dependency Upgrades
Core
Guava
v3.27.6
:bug: Bug Fixes
Core
org.assertj.core.annotation #3951:heart: Contributors
v3.27.5
:zap: Improvements
Core
Commits
e840716
[maven-release-plugin] prepare release assertj-build-3.27.785ca7eb
Deprecate XmlStringPrettyFormatter77081dc
Merge commit from forkb68fc24
Bump github/codeql-action from 4.31.9 to 4.31.10 in the github-actions
group ...0cf5bb6
Bump kotlin.version from 2.1.0 to 2.2.21d393ef1
Abort tests when symbolic links cannot be created (#3788)2212433
Add IntelliJ custom inspection for test class names5717d02
Update JetBrains icona8ec20b
Add icon for JetBrains productsc05fb3d
Bump Maven to 3.9.12 and Wrapper to 3.3.4
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show Release notes
Logback 1.5.26
Commits
33deb54
prepare release 1.5.26d38a3e2
refactoring based on usage in logback-access4368333
move VersionUtil.getCoreVersionBySelfDeclaredProperties to
CoreVersionUtil8bd5660
modify VersionCheckTest to use logback-core 1.5.257a8f0b6
version information is self declared by modules.00d272f
Do not use javax.naming namespace in the catch block, so that Logback
can be ...420d67c
mention country only, add missing 2016-03-29033aba4
fix javadoc errors6d52744
start work on 1.5.26-SNAPSHOT
Updates `ch.qos.logback:logback-core` from 1.5.25 to 1.5.26
Release notes
Logback 1.5.26
Commits
33deb54
prepare release 1.5.26d38a3e2
refactoring based on usage in logback-access4368333
move VersionUtil.getCoreVersionBySelfDeclaredProperties to
CoreVersionUtil8bd5660
modify VersionCheckTest to use logback-core 1.5.257a8f0b6
version information is self declared by modules.00d272f
Do not use javax.naming namespace in the catch block, so that Logback
can be ...420d67c
mention country only, add missing 2016-03-29033aba4
fix javadoc errors6d52744
start work on 1.5.26-SNAPSHOT
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show Commits
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show Release notes
v5.21.0
5.21.0
v5.20.0
5.20.0
v5.19.0
5.19.0
Commits
09d2230
Bump graalvm/setup-graalvm from 1.4.3 to 1.4.4 (#3768)df3e0cc
Bump graalvm/setup-graalvm from 1.4.2 to 1.4.3 (#3767)04a6e9f
Bump actions/checkout from 5 to 6 (#3765)756a3cf
Add description of matchers to potential mismatch (#3760)58ba445
Forbid mocking WeakReference with inline mock maker (#3759)966d600
Bump actions/upload-artifact from 4 to 5 (#3756)632bf7b
Bump graalvm/setup-graalvm from 1.4.1 to 1.4.2 (#3755)8564b43
Fix primitives support in GenericArrayReturnType for Android (#3753)bf3a809
Bump graalvm/setup-graalvm from 1.4.0 to 1.4.1 (#3744)cffddd4
Bump gradle/actions from 4 to 5 (#3743)
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show autoCloseables
* when close is called.
*/
- public static AutoCloseable all(final Collection extends AutoCloseable> autoCloseables) {
+ public static AutoCloseable all(
+ final @Nullable Collection extends @Nullable AutoCloseable> autoCloseables) {
return new AutoCloseable() {
@Override
public void close() throws Exception {
@@ -48,7 +51,10 @@ public void close() throws Exception {
* @param t the throwable to add suppressed exception to
* @param autoCloseables the closeables to close
*/
- public static void close(Throwable t, AutoCloseable... autoCloseables) {
+ public static void close(Throwable t, @Nullable AutoCloseable... autoCloseables) {
+ if (autoCloseables == null) {
+ return;
+ }
close(t, Arrays.asList(autoCloseables));
}
@@ -58,7 +64,8 @@ public static void close(Throwable t, AutoCloseable... autoCloseables) {
* @param t the throwable to add suppressed exception to
* @param autoCloseables the closeables to close
*/
- public static void close(Throwable t, Iterable extends AutoCloseable> autoCloseables) {
+ public static void close(
+ Throwable t, @Nullable Iterable extends @Nullable AutoCloseable> autoCloseables) {
try {
close(autoCloseables);
} catch (Exception e) {
@@ -71,7 +78,10 @@ public static void close(Throwable t, Iterable extends AutoCloseable> autoClos
*
* @param autoCloseables the closeables to close
*/
- public static void close(AutoCloseable... autoCloseables) throws Exception {
+ public static void close(@Nullable AutoCloseable... autoCloseables) throws Exception {
+ if (autoCloseables == null) {
+ return;
+ }
close(Arrays.asList(autoCloseables));
}
@@ -80,7 +90,8 @@ public static void close(AutoCloseable... autoCloseables) throws Exception {
*
* @param ac the closeables to close
*/
- public static void close(Iterable extends AutoCloseable> ac) throws Exception {
+ public static void close(@Nullable Iterable extends @Nullable AutoCloseable> ac)
+ throws Exception {
// this method can be called on a single object if it implements Iterableac to a {@link Iterable} filtering out any null values. */
- public static Iterablelist to the rollback list. */
- public void addAll(AutoCloseable... list) {
+ public void addAll(@Nullable AutoCloseable... list) {
+ if (list == null) {
+ return;
+ }
closeables.addAll(Arrays.asList(list));
}
/** Add all of list to the rollback list. */
- public void addAll(Iterable extends AutoCloseable> list) {
+ public void addAll(@Nullable Iterable extends @Nullable AutoCloseable> list) {
+ if (list == null) {
+ return;
+ }
for (AutoCloseable ac : list) {
closeables.add(ac);
}
@@ -189,7 +214,7 @@ public void close() throws Exception {
}
/** Creates an {@link RollbackCloseable} from the given closeables. */
- public static RollbackCloseable rollbackable(AutoCloseable... closeables) {
+ public static RollbackCloseable rollbackable(@Nullable AutoCloseable... closeables) {
return new RollbackCloseable(closeables);
}
@@ -203,7 +228,7 @@ public static RollbackCloseable rollbackable(AutoCloseable... closeables) {
* @throws RuntimeException if an Exception occurs; the Exception is wrapped by the
* RuntimeException
*/
- public static void closeNoChecked(final AutoCloseable autoCloseable) {
+ public static void closeNoChecked(final @Nullable AutoCloseable autoCloseable) {
if (autoCloseable != null) {
try {
autoCloseable.close();
diff --git a/memory/memory-core/src/test/java/org/apache/arrow/util/TestAutoCloseables.java b/memory/memory-core/src/test/java/org/apache/arrow/util/TestAutoCloseables.java
new file mode 100644
index 0000000000..ba5b78178a
--- /dev/null
+++ b/memory/memory-core/src/test/java/org/apache/arrow/util/TestAutoCloseables.java
@@ -0,0 +1,268 @@
+/*
+ * 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.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+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.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+public class TestAutoCloseables {
+
+ /** Closeable that records that it was closed and can optionally throw. */
+ private static final class TrackCloseable implements AutoCloseable {
+ private boolean closed;
+ private final Exception toThrow;
+
+ TrackCloseable() {
+ this.toThrow = null;
+ }
+
+ TrackCloseable(Exception toThrow) {
+ this.toThrow = toThrow;
+ }
+
+ @Override
+ public void close() throws Exception {
+ closed = true;
+ if (toThrow != null) {
+ throw toThrow;
+ }
+ }
+
+ boolean isClosed() {
+ return closed;
+ }
+ }
+
+ @Test
+ public void testCloseVarargsIgnoresNulls() throws Exception {
+ TrackCloseable a = new TrackCloseable();
+ TrackCloseable b = new TrackCloseable();
+ AutoCloseables.close(a, null, b);
+ assertTrue(a.isClosed());
+ assertTrue(b.isClosed());
+ }
+
+ @Test
+ public void testCloseVarargsThrowsFirstExceptionAndSuppressesRest() throws Exception {
+ Exception e1 = new Exception("first");
+ Exception e2 = new Exception("second");
+ TrackCloseable c1 = new TrackCloseable(e1);
+ TrackCloseable c2 = new TrackCloseable(e2);
+ Exception thrown = assertThrows(Exception.class, () -> AutoCloseables.close(c1, c2));
+ assertEquals("first", thrown.getMessage());
+ assertTrue(Arrays.asList(thrown.getSuppressed()).contains(e2));
+ }
+
+ @Test
+ public void testCloseIterableNullIterableReturns() throws Exception {
+ AutoCloseables.close((ListDependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show
Sourced from com.gradle:common-custom-user-data-maven-extension's releases.
2.1.0
- [NEW] Add support for evaluating one or more Groovy scripts in the Develocity storage directory
2.0.7
- [FIX] Added a null-safety check to handle cases where the Maven session may be null
2.0.6
- [FIX] GitHub Actions build link doesn't include run attempt
2.0.5
- [FIX] Add GitHub run attempt as custom value to precisely identify GitHub Action run
2.0.4
- [FIX] Add GitHub run number as custom value to precisely identify GitHub Action run
0bb5838
[maven-release-plugin] prepare release v2.1.04b1a27c
Update changes.mdb9010d0
Merge pull request #329
from gradle/erichaagdev/groovy-scripts-m2-directory82281ec
Switch Groovy script evaluation order7cdb3cc
Clarify script locations in README2c511ae
Add support for evaluating one or more Groovy scripts in the Develocity
stora...f02dbca
Update to use version 2.0.7 of the Common Custom User Data Maven
Extension88e0f41
Prepare for next round of development36edaf8
[maven-release-plugin] prepare for next development iteration683a966
[maven-release-plugin] prepare release v2.0.7