From 5b2a2d5fe720e413ffbe823c9e34910ba21238e1 Mon Sep 17 00:00:00 2001 From: Robert Kruszewski Date: Thu, 23 Jul 2026 13:05:23 +0100 Subject: [PATCH 1/4] vortex-jni includes vortex-geo, jni arrow import/export uses session methods Signed-off-by: Robert Kruszewski --- Cargo.lock | 1 + .../main/java/dev/vortex/api/DataSource.java | 2 +- .../src/main/java/dev/vortex/api/Scan.java | 2 +- .../java/dev/vortex/jni/NativeDataSource.java | 9 +- .../main/java/dev/vortex/jni/NativeScan.java | 9 +- .../java/dev/vortex/api/GeoTypesTest.java | 165 ++++++++++++++++++ .../java/dev/vortex/jni/JNIWriterTest.java | 6 +- vortex-jni/Cargo.toml | 1 + vortex-jni/src/data_source.rs | 16 +- vortex-jni/src/dtype.rs | 31 ---- vortex-jni/src/lib.rs | 1 - vortex-jni/src/scan.rs | 18 +- vortex-jni/src/session.rs | 1 + vortex-jni/src/writer.rs | 8 +- 14 files changed, 218 insertions(+), 52 deletions(-) create mode 100644 java/vortex-jni/src/test/java/dev/vortex/api/GeoTypesTest.java delete mode 100644 vortex-jni/src/dtype.rs diff --git a/Cargo.lock b/Cargo.lock index ab869e730b9..08ce8ca21e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10353,6 +10353,7 @@ dependencies = [ "url", "vortex", "vortex-arrow", + "vortex-geo", "vortex-parquet-variant", ] diff --git a/java/vortex-jni/src/main/java/dev/vortex/api/DataSource.java b/java/vortex-jni/src/main/java/dev/vortex/api/DataSource.java index 9a888f224f9..8658cf5198b 100644 --- a/java/vortex-jni/src/main/java/dev/vortex/api/DataSource.java +++ b/java/vortex-jni/src/main/java/dev/vortex/api/DataSource.java @@ -120,7 +120,7 @@ public static DataSource open(Session session, List readables, i /** Arrow schema of the data source (and of scans produced from it). */ public Schema arrowSchema(BufferAllocator allocator) { try (ArrowSchema schema = ArrowSchema.allocateNew(allocator)) { - NativeDataSource.arrowSchema(pointer, schema.memoryAddress()); + NativeDataSource.arrowSchema(session.nativePointer(), pointer, schema.memoryAddress()); return Data.importSchema(allocator, schema, null); } } diff --git a/java/vortex-jni/src/main/java/dev/vortex/api/Scan.java b/java/vortex-jni/src/main/java/dev/vortex/api/Scan.java index 0e1cf94349a..22a26fed365 100644 --- a/java/vortex-jni/src/main/java/dev/vortex/api/Scan.java +++ b/java/vortex-jni/src/main/java/dev/vortex/api/Scan.java @@ -42,7 +42,7 @@ static Scan fromPointer(Session session, long pointer) { */ public Schema arrowSchema(BufferAllocator allocator) { try (ArrowSchema schema = ArrowSchema.allocateNew(allocator)) { - NativeScan.arrowSchema(pointer, schema.memoryAddress()); + NativeScan.arrowSchema(session.nativePointer(), pointer, schema.memoryAddress()); return Data.importSchema(allocator, schema, null); } } diff --git a/java/vortex-jni/src/main/java/dev/vortex/jni/NativeDataSource.java b/java/vortex-jni/src/main/java/dev/vortex/jni/NativeDataSource.java index 76d09baf42e..db05d85fc17 100644 --- a/java/vortex-jni/src/main/java/dev/vortex/jni/NativeDataSource.java +++ b/java/vortex-jni/src/main/java/dev/vortex/jni/NativeDataSource.java @@ -40,8 +40,13 @@ public static native long openFiles( /** Free a data source pointer. */ public static native void free(long pointer); - /** Export the data source's schema into the Arrow C Data Interface struct at {@code schemaAddress}. */ - public static native void arrowSchema(long pointer, long schemaAddress); + /** + * Export the data source's schema into the Arrow C Data Interface struct at {@code schemaAddress}. Extension + * dtypes are dispatched through the session's registered Arrow export plugins. + * + * @param sessionPointer pointer from {@link NativeSession#newSession()} + */ + public static native void arrowSchema(long sessionPointer, long pointer, long schemaAddress); /** * Populate {@code out} with {@code [rows, cardinality]}. Cardinality is one of {@code 0=unknown}, diff --git a/java/vortex-jni/src/main/java/dev/vortex/jni/NativeScan.java b/java/vortex-jni/src/main/java/dev/vortex/jni/NativeScan.java index 86b4b2e3359..ff80b30caaa 100644 --- a/java/vortex-jni/src/main/java/dev/vortex/jni/NativeScan.java +++ b/java/vortex-jni/src/main/java/dev/vortex/jni/NativeScan.java @@ -43,8 +43,13 @@ public static native long create( /** Free a scan pointer. */ public static native void free(long pointer); - /** Export the scan's schema into the Arrow C Data Interface struct at {@code schemaAddress}. */ - public static native void arrowSchema(long pointer, long schemaAddress); + /** + * Export the scan's schema into the Arrow C Data Interface struct at {@code schemaAddress}. Extension dtypes are + * dispatched through the session's registered Arrow export plugins. + * + * @param sessionPointer pointer from {@link NativeSession#newSession()} + */ + public static native void arrowSchema(long sessionPointer, long pointer, long schemaAddress); /** Fill {@code out} with {@code [count, cardinality]}. */ public static native void partitionCount(long pointer, long[] out); diff --git a/java/vortex-jni/src/test/java/dev/vortex/api/GeoTypesTest.java b/java/vortex-jni/src/test/java/dev/vortex/api/GeoTypesTest.java new file mode 100644 index 00000000000..d02f216757e --- /dev/null +++ b/java/vortex-jni/src/test/java/dev/vortex/api/GeoTypesTest.java @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +package dev.vortex.api; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.vortex.arrow.ArrowAllocation; +import dev.vortex.jni.NativeLoader; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.arrow.c.ArrowArray; +import org.apache.arrow.c.ArrowSchema; +import org.apache.arrow.c.Data; +import org.apache.arrow.memory.BufferAllocator; +import org.apache.arrow.vector.VarBinaryVector; +import org.apache.arrow.vector.VectorSchemaRoot; +import org.apache.arrow.vector.ipc.ArrowReader; +import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.arrow.vector.types.pojo.Field; +import org.apache.arrow.vector.types.pojo.FieldType; +import org.apache.arrow.vector.types.pojo.Schema; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Round-trips a Vortex geo extension column ({@code vortex.geo.wkb}) through the JNI boundary. + * Geo columns cross the boundary as Arrow fields tagged with the GeoArrow extension name + * ({@code geoarrow.wkb}) and JSON metadata carrying the CRS. + */ +public final class GeoTypesTest { + private static final String EXTENSION_NAME_KEY = "ARROW:extension:name"; + private static final String EXTENSION_METADATA_KEY = "ARROW:extension:metadata"; + private static final String GEOARROW_WKB = "geoarrow.wkb"; + private static final String CRS_METADATA = "{\"crs\":\"OGC:CRS84\"}"; + + @TempDir + static Path tempDir; + + static String writePath; + + private static final List WKB_POINTS = new ArrayList<>(); + + @BeforeAll + public static void loadLibrary() { + NativeLoader.loadJni(); + } + + @BeforeAll + static void setup() throws IOException { + writePath = tempDir.resolve("geo.vortex").toAbsolutePath().toUri().toString(); + + WKB_POINTS.add(wkbPoint(1.0, 2.0)); + WKB_POINTS.add(wkbPoint(-111.7610, 34.8697)); + WKB_POINTS.add(null); + WKB_POINTS.add(wkbPoint(0.0, 0.0)); + + BufferAllocator allocator = ArrowAllocation.rootAllocator(); + Schema schema = new Schema(List.of(wkbField("geom"))); + Session session = Session.create(); + try (VortexWriter writer = VortexWriter.create(session, writePath, schema, new HashMap<>(), allocator); + VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) { + VarBinaryVector geomVec = (VarBinaryVector) root.getVector("geom"); + geomVec.allocateNew(WKB_POINTS.size()); + for (int i = 0; i < WKB_POINTS.size(); i++) { + byte[] wkb = WKB_POINTS.get(i); + if (wkb == null) { + geomVec.setNull(i); + } else { + geomVec.setSafe(i, wkb); + } + } + root.setRowCount(WKB_POINTS.size()); + + try (ArrowArray arrowArray = ArrowArray.allocateNew(allocator); + ArrowSchema arrowSchemaFfi = ArrowSchema.allocateNew(allocator)) { + Data.exportVectorSchemaRoot(allocator, root, null, arrowArray, arrowSchemaFfi); + writer.writeBatch(arrowArray.memoryAddress(), arrowSchemaFfi.memoryAddress()); + } + } + } + + @Test + public void testSchemaCarriesGeoArrowExtension() { + BufferAllocator allocator = ArrowAllocation.rootAllocator(); + Session session = Session.create(); + DataSource ds = DataSource.open(session, writePath); + + Schema schema = ds.arrowSchema(allocator); + Field geom = schema.findField("geom"); + assertEquals(GEOARROW_WKB, geom.getMetadata().get(EXTENSION_NAME_KEY)); + assertEquals(CRS_METADATA, geom.getMetadata().get(EXTENSION_METADATA_KEY)); + assertTrue(geom.isNullable()); + } + + @Test + public void testScanSchemaCarriesGeoArrowExtension() { + BufferAllocator allocator = ArrowAllocation.rootAllocator(); + Session session = Session.create(); + DataSource ds = DataSource.open(session, writePath); + + Schema schema = ds.scan(ScanOptions.of()).arrowSchema(allocator); + Field geom = schema.findField("geom"); + assertEquals(GEOARROW_WKB, geom.getMetadata().get(EXTENSION_NAME_KEY)); + assertEquals(CRS_METADATA, geom.getMetadata().get(EXTENSION_METADATA_KEY)); + } + + @Test + public void testWkbValuesRoundTrip() throws Exception { + BufferAllocator allocator = ArrowAllocation.rootAllocator(); + Session session = Session.create(); + DataSource ds = DataSource.open(session, writePath); + + List values = new ArrayList<>(); + Scan scan = ds.scan(ScanOptions.of()); + while (scan.hasNext()) { + Partition partition = scan.next(); + try (ArrowReader arrowReader = partition.scanArrow(allocator)) { + while (arrowReader.loadNextBatch()) { + VectorSchemaRoot root = arrowReader.getVectorSchemaRoot(); + var geomVec = root.getVector("geom"); + for (int i = 0; i < root.getRowCount(); i++) { + values.add(geomVec.isNull(i) ? null : (byte[]) geomVec.getObject(i)); + } + } + } + } + + assertEquals(WKB_POINTS.size(), values.size()); + for (int i = 0; i < WKB_POINTS.size(); i++) { + byte[] expected = WKB_POINTS.get(i); + if (expected == null) { + assertNull(values.get(i)); + } else { + assertArrayEquals(expected, values.get(i)); + } + } + } + + private static Field wkbField(String name) { + Map metadata = + Map.of(EXTENSION_NAME_KEY, GEOARROW_WKB, EXTENSION_METADATA_KEY, CRS_METADATA); + return new Field(name, new FieldType(true, ArrowType.Binary.INSTANCE, null, metadata), null); + } + + /** Little-endian WKB encoding of {@code POINT(x y)}. */ + private static byte[] wkbPoint(double x, double y) { + ByteBuffer buffer = ByteBuffer.allocate(21).order(ByteOrder.LITTLE_ENDIAN); + buffer.put((byte) 1); // little-endian marker + buffer.putInt(1); // geometry type: point + buffer.putDouble(x); + buffer.putDouble(y); + return buffer.array(); + } +} diff --git a/java/vortex-jni/src/test/java/dev/vortex/jni/JNIWriterTest.java b/java/vortex-jni/src/test/java/dev/vortex/jni/JNIWriterTest.java index 1bb51f8799f..59132b78168 100644 --- a/java/vortex-jni/src/test/java/dev/vortex/jni/JNIWriterTest.java +++ b/java/vortex-jni/src/test/java/dev/vortex/jni/JNIWriterTest.java @@ -31,6 +31,7 @@ import org.apache.arrow.vector.VarBinaryVector; import org.apache.arrow.vector.VarCharVector; import org.apache.arrow.vector.VectorSchemaRoot; +import org.apache.arrow.vector.ViewVarBinaryVector; import org.apache.arrow.vector.ViewVarCharVector; import org.apache.arrow.vector.complex.StructVector; import org.apache.arrow.vector.ipc.ArrowReader; @@ -261,8 +262,9 @@ public void testParquetVariantRoundTrip() throws IOException { assertTrue(reader.loadNextBatch()); VectorSchemaRoot resultRoot = reader.getVectorSchemaRoot(); StructVector variant = (StructVector) resultRoot.getVector("variant"); - VarBinaryVector metadata = variant.getChild("metadata", VarBinaryVector.class); - VarBinaryVector value = variant.getChild("value", VarBinaryVector.class); + // Binary columns cross the boundary as their native view types. + ViewVarBinaryVector metadata = variant.getChild("metadata", ViewVarBinaryVector.class); + ViewVarBinaryVector value = variant.getChild("value", ViewVarBinaryVector.class); assertArrayEquals(VARIANT_METADATA, metadata.get(0)); assertArrayEquals(VARIANT_INT8_42, value.get(0)); diff --git a/vortex-jni/Cargo.toml b/vortex-jni/Cargo.toml index 6bb50cf097d..50e30bb5aab 100644 --- a/vortex-jni/Cargo.toml +++ b/vortex-jni/Cargo.toml @@ -33,6 +33,7 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } url = { workspace = true } vortex = { workspace = true, features = ["object_store", "files"] } vortex-arrow = { workspace = true } +vortex-geo = { workspace = true } vortex-parquet-variant = { workspace = true } [dev-dependencies] diff --git a/vortex-jni/src/data_source.rs b/vortex-jni/src/data_source.rs index 47d9d83c577..0dfe0b8b301 100644 --- a/vortex-jni/src/data_source.rs +++ b/vortex-jni/src/data_source.rs @@ -8,8 +8,10 @@ //! and bare file paths are both accepted. Filesystems are cached per base URL so repeated //! globs against the same bucket share a single client. +use std::ptr; use std::sync::Arc; +use arrow_array::ffi::FFI_ArrowSchema; use jni::EnvUnowned; use jni::objects::JClass; use jni::objects::JLongArray; @@ -29,9 +31,9 @@ use vortex::io::runtime::BlockingRuntime; use vortex::io::session::RuntimeSessionExt; use vortex::scan::DataSourceRef; use vortex::utils::aliases::hash_map::HashMap; +use vortex_arrow::ArrowSessionExt; use crate::RUNTIME; -use crate::dtype::export_dtype_to_arrow; use crate::errors::try_or_throw; use crate::file::extract_properties; use crate::io::JavaFileSystem; @@ -208,11 +210,14 @@ pub extern "system" fn Java_dev_vortex_jni_NativeDataSource_free( } /// Export the data source's schema into the Arrow C Data Interface schema struct at -/// `schema_addr`. +/// `schema_addr`. Extension dtypes (e.g. the `vortex.geo.*` family) are dispatched through +/// the session's registered Arrow export plugins, so their `ARROW:extension:name`/ +/// `ARROW:extension:metadata` survive the FFI crossing. #[unsafe(no_mangle)] pub extern "system" fn Java_dev_vortex_jni_NativeDataSource_arrowSchema( mut env: EnvUnowned, _class: JClass, + session_ptr: jlong, pointer: jlong, schema_addr: jlong, ) { @@ -220,8 +225,13 @@ pub extern "system" fn Java_dev_vortex_jni_NativeDataSource_arrowSchema( if schema_addr == 0 { throw_runtime!("null arrow schema address"); } + let session = unsafe { session_ref(session_ptr) }; let ds = unsafe { NativeDataSource::from_ptr(pointer) }; - export_dtype_to_arrow(ds.inner.dtype(), schema_addr)?; + let arrow_schema = session.arrow().to_arrow_schema(ds.inner.dtype())?; + let ffi_schema = FFI_ArrowSchema::try_from(&arrow_schema)?; + unsafe { + ptr::write(schema_addr as *mut FFI_ArrowSchema, ffi_schema); + } Ok(()) }); } diff --git a/vortex-jni/src/dtype.rs b/vortex-jni/src/dtype.rs deleted file mode 100644 index 1014ae07613..00000000000 --- a/vortex-jni/src/dtype.rs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: Copyright the Vortex contributors - -//! Bridge functions between a Vortex [`DType`] and Arrow's C Data Interface -//! [`FFI_ArrowSchema`]. Java receives DType information exclusively as Arrow schema. - -use std::ptr; - -use arrow_array::ffi::FFI_ArrowSchema; -use arrow_schema::Schema; -use vortex::dtype::DType; -use vortex::error::VortexResult; -use vortex_arrow::ToArrowType; - -/// Export a Vortex [`DType`] to the Arrow C Data Interface struct at `schema_addr`. String and -/// binary columns are exported as their native view types (Utf8View/BinaryView); consumers are -/// expected to handle them. -pub(crate) fn export_dtype_to_arrow(dtype: &DType, schema_addr: i64) -> VortexResult<()> { - let arrow_schema = dtype.to_arrow_schema()?; - let ffi_schema = FFI_ArrowSchema::try_from(&arrow_schema)?; - unsafe { - ptr::write(schema_addr as *mut FFI_ArrowSchema, ffi_schema); - } - Ok(()) -} - -/// Decode an [`FFI_ArrowSchema`] pointed to by `schema_addr` into an Arrow [`Schema`]. -pub(crate) fn import_arrow_schema(schema_addr: i64) -> VortexResult { - let ffi_schema = unsafe { &*(schema_addr as *const FFI_ArrowSchema) }; - Ok(Schema::try_from(ffi_schema)?) -} diff --git a/vortex-jni/src/lib.rs b/vortex-jni/src/lib.rs index c8369c28bff..4e74eb304ce 100644 --- a/vortex-jni/src/lib.rs +++ b/vortex-jni/src/lib.rs @@ -19,7 +19,6 @@ macro_rules! throw_runtime { } mod data_source; -mod dtype; mod errors; mod expression; mod file; diff --git a/vortex-jni/src/scan.rs b/vortex-jni/src/scan.rs index 4b067706eba..c239ecca74a 100644 --- a/vortex-jni/src/scan.rs +++ b/vortex-jni/src/scan.rs @@ -17,6 +17,7 @@ use std::sync::Arc; use arrow_array::RecordBatch; use arrow_array::cast::AsArray; +use arrow_array::ffi::FFI_ArrowSchema; use arrow_array::ffi_stream::FFI_ArrowArrayStream; use arrow_schema::ArrowError; use arrow_schema::Field; @@ -43,12 +44,10 @@ use vortex::scan::PartitionStream; use vortex::scan::ScanRequest; use vortex::scan::selection::Selection; use vortex_arrow::ArrowSessionExt; -use vortex_arrow::ToArrowType; use crate::POOL; use crate::RUNTIME; use crate::data_source::NativeDataSource; -use crate::dtype::export_dtype_to_arrow; use crate::errors::try_or_throw; use crate::session::session_ref; @@ -200,10 +199,13 @@ pub extern "system" fn Java_dev_vortex_jni_NativeScan_free( } /// Write the scan's DType as an Arrow schema to the FFI struct at `schema_addr`. +/// Extension dtypes are dispatched through the session's registered Arrow export plugins, +/// so their `ARROW:extension:name`/`ARROW:extension:metadata` survive the FFI crossing. #[unsafe(no_mangle)] pub extern "system" fn Java_dev_vortex_jni_NativeScan_arrowSchema( mut env: EnvUnowned, _class: JClass, + session_ptr: jlong, pointer: jlong, schema_addr: jlong, ) { @@ -211,11 +213,16 @@ pub extern "system" fn Java_dev_vortex_jni_NativeScan_arrowSchema( if schema_addr == 0 { throw_runtime!("null arrow schema address"); } + let session = unsafe { session_ref(session_ptr) }; let scan = unsafe { &*(pointer as *const NativeScan) }; let NativeScan::Pending(scan) = scan else { throw_runtime!("schema unavailable: scan already started"); }; - export_dtype_to_arrow(scan.dtype(), schema_addr)?; + let arrow_schema = session.arrow().to_arrow_schema(scan.dtype())?; + let ffi_schema = FFI_ArrowSchema::try_from(&arrow_schema)?; + unsafe { + ptr::write(schema_addr as *mut FFI_ArrowSchema, ffi_schema); + } Ok(()) }); } @@ -343,10 +350,9 @@ pub extern "system" fn Java_dev_vortex_jni_NativePartition_scanArrow( let array_stream = partition.execute()?; let dtype = array_stream.dtype().clone(); - let schema = Arc::new(dtype.to_arrow_schema()?); - let target = Arc::new(Field::new_struct("", schema.fields().clone(), false)); - let session = unsafe { session_ref(session_ptr) }; + let schema = Arc::new(session.arrow().to_arrow_schema(&dtype)?); + let target = Arc::new(Field::new_struct("", schema.fields().clone(), false)); let iter = RUNTIME .block_on_stream_thread_safe(|handle| { diff --git a/vortex-jni/src/session.rs b/vortex-jni/src/session.rs index 9adaf544431..881babc5dea 100644 --- a/vortex-jni/src/session.rs +++ b/vortex-jni/src/session.rs @@ -18,6 +18,7 @@ use crate::RUNTIME; pub(crate) fn new_session() -> Box { let session = VortexSession::default().with_handle(RUNTIME.handle()); vortex_parquet_variant::initialize(&session); + vortex_geo::initialize(&session); Box::new(session) } diff --git a/vortex-jni/src/writer.rs b/vortex-jni/src/writer.rs index 53649eef4ab..f257965463a 100644 --- a/vortex-jni/src/writer.rs +++ b/vortex-jni/src/writer.rs @@ -15,6 +15,7 @@ use arrow_array::RecordBatch; use arrow_array::StructArray; use arrow_array::ffi::FFI_ArrowArray; use arrow_array::ffi::FFI_ArrowSchema; +use arrow_schema::Schema; use arrow_schema::SchemaRef; use async_fs::File; use futures::SinkExt; @@ -64,7 +65,6 @@ use vortex_arrow::ArrowSessionExt; use vortex_parquet_variant::ParquetVariant; use crate::RUNTIME; -use crate::dtype::import_arrow_schema; use crate::errors::JNIError; use crate::errors::try_or_throw; use crate::file::extract_properties; @@ -405,7 +405,8 @@ pub extern "system" fn Java_dev_vortex_jni_NativeWriter_create( } let session = unsafe { session_ref(session_ptr) }; - let arrow_schema = Arc::new(import_arrow_schema(arrow_schema_addr)?); + let ffi_schema = unsafe { &*(arrow_schema_addr as *const FFI_ArrowSchema) }; + let arrow_schema = Arc::new(Schema::try_from(ffi_schema)?); let write_schema = session.arrow().from_arrow_schema(arrow_schema.as_ref())?; let file_path: String = uri.try_to_string(env)?; @@ -484,7 +485,8 @@ pub extern "system" fn Java_dev_vortex_jni_NativeWriter_createStream( } let session = unsafe { session_ref(session_ptr) }; - let arrow_schema = Arc::new(import_arrow_schema(arrow_schema_addr)?); + let ffi_schema = unsafe { &*(arrow_schema_addr as *const FFI_ArrowSchema) }; + let arrow_schema = Arc::new(Schema::try_from(ffi_schema)?); let write_schema = session.arrow().from_arrow_schema(arrow_schema.as_ref())?; let vm = env.get_java_vm()?; From 16de8f982e08c8a216ce3926f45457c10e2f414d Mon Sep 17 00:00:00 2001 From: Robert Kruszewski Date: Thu, 23 Jul 2026 13:07:15 +0100 Subject: [PATCH 2/4] Update scan.rs Signed-off-by: Robert Kruszewski --- vortex-jni/src/scan.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/vortex-jni/src/scan.rs b/vortex-jni/src/scan.rs index c239ecca74a..82fa23fa650 100644 --- a/vortex-jni/src/scan.rs +++ b/vortex-jni/src/scan.rs @@ -199,8 +199,6 @@ pub extern "system" fn Java_dev_vortex_jni_NativeScan_free( } /// Write the scan's DType as an Arrow schema to the FFI struct at `schema_addr`. -/// Extension dtypes are dispatched through the session's registered Arrow export plugins, -/// so their `ARROW:extension:name`/`ARROW:extension:metadata` survive the FFI crossing. #[unsafe(no_mangle)] pub extern "system" fn Java_dev_vortex_jni_NativeScan_arrowSchema( mut env: EnvUnowned, From c2e6e803228953c6d941e330b97711f27aa31676 Mon Sep 17 00:00:00 2001 From: Robert Kruszewski Date: Thu, 23 Jul 2026 13:08:06 +0100 Subject: [PATCH 3/4] Update data_source.rs Signed-off-by: Robert Kruszewski --- vortex-jni/src/data_source.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/vortex-jni/src/data_source.rs b/vortex-jni/src/data_source.rs index 0dfe0b8b301..516a0aa7f55 100644 --- a/vortex-jni/src/data_source.rs +++ b/vortex-jni/src/data_source.rs @@ -210,9 +210,6 @@ pub extern "system" fn Java_dev_vortex_jni_NativeDataSource_free( } /// Export the data source's schema into the Arrow C Data Interface schema struct at -/// `schema_addr`. Extension dtypes (e.g. the `vortex.geo.*` family) are dispatched through -/// the session's registered Arrow export plugins, so their `ARROW:extension:name`/ -/// `ARROW:extension:metadata` survive the FFI crossing. #[unsafe(no_mangle)] pub extern "system" fn Java_dev_vortex_jni_NativeDataSource_arrowSchema( mut env: EnvUnowned, From 09897628f4a702738242d86bda6b9b21b86910de Mon Sep 17 00:00:00 2001 From: Robert Kruszewski Date: Fri, 24 Jul 2026 12:14:07 +0100 Subject: [PATCH 4/4] format Signed-off-by: Robert Kruszewski --- .../src/main/java/dev/vortex/jni/NativeDataSource.java | 4 ++-- .../src/test/java/dev/vortex/api/GeoTypesTest.java | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/java/vortex-jni/src/main/java/dev/vortex/jni/NativeDataSource.java b/java/vortex-jni/src/main/java/dev/vortex/jni/NativeDataSource.java index db05d85fc17..7ceaf44894b 100644 --- a/java/vortex-jni/src/main/java/dev/vortex/jni/NativeDataSource.java +++ b/java/vortex-jni/src/main/java/dev/vortex/jni/NativeDataSource.java @@ -41,8 +41,8 @@ public static native long openFiles( public static native void free(long pointer); /** - * Export the data source's schema into the Arrow C Data Interface struct at {@code schemaAddress}. Extension - * dtypes are dispatched through the session's registered Arrow export plugins. + * Export the data source's schema into the Arrow C Data Interface struct at {@code schemaAddress}. Extension dtypes + * are dispatched through the session's registered Arrow export plugins. * * @param sessionPointer pointer from {@link NativeSession#newSession()} */ diff --git a/java/vortex-jni/src/test/java/dev/vortex/api/GeoTypesTest.java b/java/vortex-jni/src/test/java/dev/vortex/api/GeoTypesTest.java index d02f216757e..bdc13f5b3b4 100644 --- a/java/vortex-jni/src/test/java/dev/vortex/api/GeoTypesTest.java +++ b/java/vortex-jni/src/test/java/dev/vortex/api/GeoTypesTest.java @@ -34,9 +34,9 @@ import org.junit.jupiter.api.io.TempDir; /** - * Round-trips a Vortex geo extension column ({@code vortex.geo.wkb}) through the JNI boundary. - * Geo columns cross the boundary as Arrow fields tagged with the GeoArrow extension name - * ({@code geoarrow.wkb}) and JSON metadata carrying the CRS. + * Round-trips a Vortex geo extension column ({@code vortex.geo.wkb}) through the JNI boundary. Geo columns cross the + * boundary as Arrow fields tagged with the GeoArrow extension name ({@code geoarrow.wkb}) and JSON metadata carrying + * the CRS. */ public final class GeoTypesTest { private static final String EXTENSION_NAME_KEY = "ARROW:extension:name"; @@ -148,8 +148,7 @@ public void testWkbValuesRoundTrip() throws Exception { } private static Field wkbField(String name) { - Map metadata = - Map.of(EXTENSION_NAME_KEY, GEOARROW_WKB, EXTENSION_METADATA_KEY, CRS_METADATA); + Map metadata = Map.of(EXTENSION_NAME_KEY, GEOARROW_WKB, EXTENSION_METADATA_KEY, CRS_METADATA); return new Field(name, new FieldType(true, ArrowType.Binary.INSTANCE, null, metadata), null); }