From c3ad74d09d6c6d4e3be030e63cf8d97a07d35d37 Mon Sep 17 00:00:00 2001 From: wangyunlai Date: Tue, 20 May 2025 22:22:40 +0800 Subject: [PATCH 01/10] fix get length of byte[] --- c/src/main/cpp/jni_wrapper.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/c/src/main/cpp/jni_wrapper.cc b/c/src/main/cpp/jni_wrapper.cc index 35c2b7787e..436cbdc806 100644 --- a/c/src/main/cpp/jni_wrapper.cc +++ b/c/src/main/cpp/jni_wrapper.cc @@ -205,8 +205,9 @@ void TryCopyLastError(JNIEnv* env, InnerPrivateData* private_data) { return; } + jsize error_bytes_len = env->GetArrayLength(arr); char* error_str = reinterpret_cast(error_bytes); - private_data->last_error_ = std::string(error_str, std::strlen(error_str)); + private_data->last_error_ = std::string(error_str, error_bytes_len); env->ReleaseByteArrayElements(arr, error_bytes, JNI_ABORT); } From a4614c54cd3093e8976f7fa69367c062943b567c Mon Sep 17 00:00:00 2001 From: wangyunlai Date: Sun, 25 May 2025 17:11:35 +0800 Subject: [PATCH 02/10] add a unittest --- .../org/apache/arrow/c/ExceptionTest.java | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 c/src/test/java/org/apache/arrow/c/ExceptionTest.java diff --git a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java new file mode 100644 index 0000000000..c0211b3351 --- /dev/null +++ b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java @@ -0,0 +1,164 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.arrow.c; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.apache.arrow.memory.BufferAllocator; +import org.apache.arrow.memory.RootAllocator; +import org.apache.arrow.vector.IntVector; +import org.apache.arrow.vector.VectorLoader; +import org.apache.arrow.vector.VectorSchemaRoot; +import org.apache.arrow.vector.VectorUnloader; +import org.apache.arrow.vector.dictionary.Dictionary; +import org.apache.arrow.vector.dictionary.DictionaryProvider; +import org.apache.arrow.vector.ipc.ArrowReader; +import org.apache.arrow.vector.ipc.message.ArrowRecordBatch; +import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.arrow.vector.types.pojo.Field; +import org.apache.arrow.vector.types.pojo.Schema; +import org.junit.jupiter.api.Test; + +final class ExceptionTest { + @Test + public void testException() throws IOException { + final Schema schema = + new Schema(Collections.singletonList(Field.nullable("ints", new ArrowType.Int(32, true)))); + final List batches = new ArrayList<>(); + + try (BufferAllocator allocator = new RootAllocator(); + VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) { + final IntVector ints = (IntVector) root.getVector(0); + VectorUnloader unloader = new VectorUnloader(root); + + root.allocateNew(); + ints.setSafe(0, 1); + ints.setSafe(1, 2); + ints.setSafe(2, 4); + ints.setSafe(3, 8); + root.setRowCount(4); + batches.add(unloader.getRecordBatch()); + + RuntimeException ex = new RuntimeException("this is an exception test to test TryCopyLastError may works " + + "but the test cannot make sure there is no problem with it"); + batches.add(ex); + + ArrowReader source = new ExceptionMemoryArrowReader(allocator, schema, batches); + + try (final ArrowArrayStream stream = ArrowArrayStream.allocateNew(allocator); + final VectorSchemaRoot importRoot = VectorSchemaRoot.create(schema, allocator)) { + final VectorLoader loader = new VectorLoader(importRoot); + Data.exportArrayStream(allocator, source, stream); + + try (final ArrowReader reader = Data.importArrayStream(allocator, stream)) { + assertThat(reader.getVectorSchemaRoot().getSchema()).isEqualTo(schema); + + for (Object batch : batches) { + try { + reader.loadNextBatch(); + } catch (Exception e) { + continue; + } + loader.load((ArrowRecordBatch) batch); + + assertThat(reader.getVectorSchemaRoot().getRowCount()).isEqualTo(root.getRowCount()); + } + } + } + } + } + + static class ExceptionMemoryArrowReader extends ArrowReader { + private final Schema schema; + private final List batches; // set ArrowRecordBatch or Exception + private final DictionaryProvider provider; + private int nextBatch; + + ExceptionMemoryArrowReader( + BufferAllocator allocator, + Schema schema, + List batches) { + super(allocator); + this.schema = schema; + this.batches = batches; + this.provider = new CDataDictionaryProvider(); + this.nextBatch = 0; + } + + @Override + public Dictionary lookup(long id) { + return provider.lookup(id); + } + + @Override + public Set getDictionaryIds() { + return provider.getDictionaryIds(); + } + + @Override + public Map getDictionaryVectors() { + return getDictionaryIds().stream() + .collect(Collectors.toMap(Function.identity(), this::lookup)); + } + + @Override + public boolean loadNextBatch() throws IOException { + if (nextBatch < batches.size()) { + Object object = batches.get(nextBatch++); + if (object instanceof RuntimeException) { + throw (RuntimeException) object; + } + VectorLoader loader = new VectorLoader(getVectorSchemaRoot()); + loader.load((ArrowRecordBatch) object); + return true; + } + return false; + } + + @Override + public long bytesRead() { + return 0; + } + + @Override + protected void closeReadSource() throws IOException { + try { + for (Object object : batches) { + if (object instanceof ArrowRecordBatch) { + ArrowRecordBatch batch = (ArrowRecordBatch) object; + batch.close(); + } + } + } catch (Exception e) { + throw new IOException(e); + } + } + + @Override + protected Schema readSchema() { + return schema; + } + } +} From 8d2c4d5222e10a7c7a48e0e393a8c9caa0b6cda1 Mon Sep 17 00:00:00 2001 From: wangyunlai Date: Sat, 31 May 2025 17:29:08 +0800 Subject: [PATCH 03/10] test copy-error --- .../test/java/org/apache/arrow/c/ExceptionTest.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java index c0211b3351..c3340e7943 100644 --- a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java +++ b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java @@ -61,8 +61,9 @@ public void testException() throws IOException { root.setRowCount(4); batches.add(unloader.getRecordBatch()); - RuntimeException ex = new RuntimeException("this is an exception test to test TryCopyLastError may works " + - "but the test cannot make sure there is no problem with it"); + final String exceptionMessage = "java.lang.RuntimeException: Error occurred while getting next schema root.\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.next(ArrowVectorIterator.java:205)\n\tat com.oceanbase.external.jdbc.JdbcScanner.loadNextBatch(JdbcScanner.java:73)\n\tat org.apache.arrow.c.ArrayStreamExporter$ExportedArrayStreamPrivateData.getNext(ArrayStreamExporter.java:72)\nCaused by: java.lang.RuntimeException: Error occurred while consuming data.\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.consumeData(ArrowVectorIterator.java:127)\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.load(ArrowVectorIterator.java:178)\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.next(ArrowVectorIterator.java:198)\n\t... 2 more\nCaused by: java.lang.OutOfMemoryError: Java heap space\n"; + + RuntimeException ex = new RuntimeException(exceptionMessage); batches.add(ex); ArrowReader source = new ExceptionMemoryArrowReader(allocator, schema, batches); @@ -79,6 +80,13 @@ public void testException() throws IOException { try { reader.loadNextBatch(); } catch (Exception e) { + Throwable exToCheck = e; + while (exToCheck != null && !(exToCheck instanceof RuntimeException)) { + exToCheck = exToCheck.getCause(); + } + if (exToCheck != null) { + assertThat(exToCheck.getMessage()).isEqualTo(exceptionMessage); + } continue; } loader.load((ArrowRecordBatch) batch); From 4f158ee377d1fed45fcc8ee8773e154730bbee33 Mon Sep 17 00:00:00 2001 From: wangyunlai Date: Mon, 2 Jun 2025 17:08:09 +0800 Subject: [PATCH 04/10] test for copyerror --- c/ExceptionTest.java | 183 ++++++++++++++++++ .../org/apache/arrow/c/ExceptionTest.java | 31 ++- 2 files changed, 204 insertions(+), 10 deletions(-) create mode 100644 c/ExceptionTest.java diff --git a/c/ExceptionTest.java b/c/ExceptionTest.java new file mode 100644 index 0000000000..900d65e70f --- /dev/null +++ b/c/ExceptionTest.java @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.arrow.c; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.apache.arrow.c.jni.CDataJniException; +import org.apache.arrow.memory.BufferAllocator; +import org.apache.arrow.memory.RootAllocator; +import org.apache.arrow.vector.IntVector; +import org.apache.arrow.vector.VectorLoader; +import org.apache.arrow.vector.VectorSchemaRoot; +import org.apache.arrow.vector.VectorUnloader; +import org.apache.arrow.vector.dictionary.Dictionary; +import org.apache.arrow.vector.dictionary.DictionaryProvider; +import org.apache.arrow.vector.ipc.ArrowReader; +import org.apache.arrow.vector.ipc.message.ArrowRecordBatch; +import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.arrow.vector.types.pojo.Field; +import org.apache.arrow.vector.types.pojo.Schema; +import org.junit.jupiter.api.Test; + +// Regression test for https://github.com/apache/arrow-java/issues/759 +final class ExceptionTest { + @Test + public void testException() throws IOException { + final Schema schema = + new Schema(Collections.singletonList(Field.nullable("ints", new ArrowType.Int(32, true)))); + final List batches = new ArrayList<>(); + + try (BufferAllocator allocator = new RootAllocator(); + VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) { + final IntVector ints = (IntVector) root.getVector(0); + VectorUnloader unloader = new VectorUnloader(root); + + root.allocateNew(); + ints.setSafe(0, 1); + ints.setSafe(1, 2); + ints.setSafe(2, 4); + ints.setSafe(3, 8); + root.setRowCount(4); + batches.add(unloader.getRecordBatch()); + + final String exceptionMessage = "Error occurred while getting next schema root.\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.next(ArrowVectorIterator.java:205)\n\tat com.oceanbase.external.jdbc.JdbcScanner.loadNextBatch(JdbcScanner.java:73)\n\tat org.apache.arrow.c.ArrayStreamExporter$ExportedArrayStreamPrivateData.getNext(ArrayStreamExporter.java:72)\nCaused by: java.lang.RuntimeException: Error occurred while consuming data.\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.consumeData(ArrowVectorIterator.java:127)\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.load(ArrowVectorIterator.java:178)\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.next(ArrowVectorIterator.java:198)\n\t... 2 more\nCaused by: java.lang.OutOfMemoryError: Java heap space\n"; + + RuntimeException exToThrow = new RuntimeException(exceptionMessage); + batches.add(exToThrow); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + exToThrow.printStackTrace(pw); + final String expectExceptionMessage = sw.toString(); + + ArrowReader source = new ExceptionMemoryArrowReader(allocator, schema, batches); + + try (final ArrowArrayStream stream = ArrowArrayStream.allocateNew(allocator); + final VectorSchemaRoot importRoot = VectorSchemaRoot.create(schema, allocator)) { + final VectorLoader loader = new VectorLoader(importRoot); + Data.exportArrayStream(allocator, source, stream); + + Throwable exceptionThrowed = null; + try (final ArrowReader reader = Data.importArrayStream(allocator, stream)) { + assertThat(reader.getVectorSchemaRoot().getSchema()).isEqualTo(schema); + + for (Object batch : batches) { + try { + reader.loadNextBatch(); + } catch (Exception e) { + assertThat(exceptionThrowed).isEqualTo(null); + final String eMessage = e.getMessage(); + assertThat(eMessage.length()).isGreaterThan(expectExceptionMessage.length() + 1); // 1 for '}' + assertThat(eMessage.substring(eMessage.length() - expectExceptionMessage.length() - 1, eMessage.length() - 1)) + .isEqualTo(expectExceptionMessage); + exceptionThrowed = e; + continue; + } + loader.load((ArrowRecordBatch) batch); + + assertThat(reader.getVectorSchemaRoot().getRowCount()).isEqualTo(root.getRowCount()); + } + } + assertThat(exceptionThrowed).isNotEqualTo(null); + } + } + } + + static class ExceptionMemoryArrowReader extends ArrowReader { + private final Schema schema; + private final List batches; // set ArrowRecordBatch or Exception + private final DictionaryProvider provider; + private int nextBatch; + + ExceptionMemoryArrowReader( + BufferAllocator allocator, + Schema schema, + List batches) { + super(allocator); + this.schema = schema; + this.batches = batches; + this.provider = new CDataDictionaryProvider(); + this.nextBatch = 0; + } + + @Override + public Dictionary lookup(long id) { + return provider.lookup(id); + } + + @Override + public Set getDictionaryIds() { + return provider.getDictionaryIds(); + } + + @Override + public Map getDictionaryVectors() { + return getDictionaryIds().stream() + .collect(Collectors.toMap(Function.identity(), this::lookup)); + } + + @Override + public boolean loadNextBatch() throws IOException { + if (nextBatch < batches.size()) { + Object object = batches.get(nextBatch++); + if (object instanceof RuntimeException) { + throw (RuntimeException) object; + } + VectorLoader loader = new VectorLoader(getVectorSchemaRoot()); + loader.load((ArrowRecordBatch) object); + return true; + } + return false; + } + + @Override + public long bytesRead() { + return 0; + } + + @Override + protected void closeReadSource() throws IOException { + try { + for (Object object : batches) { + if (object instanceof ArrowRecordBatch) { + ArrowRecordBatch batch = (ArrowRecordBatch) object; + batch.close(); + } + } + } catch (Exception e) { + throw new IOException(e); + } + } + + @Override + protected Schema readSchema() { + return schema; + } + } +} diff --git a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java index c3340e7943..900d65e70f 100644 --- a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java +++ b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java @@ -19,13 +19,17 @@ import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; +import org.apache.arrow.c.jni.CDataJniException; import org.apache.arrow.memory.BufferAllocator; import org.apache.arrow.memory.RootAllocator; import org.apache.arrow.vector.IntVector; @@ -41,6 +45,7 @@ import org.apache.arrow.vector.types.pojo.Schema; import org.junit.jupiter.api.Test; +// Regression test for https://github.com/apache/arrow-java/issues/759 final class ExceptionTest { @Test public void testException() throws IOException { @@ -61,10 +66,15 @@ public void testException() throws IOException { root.setRowCount(4); batches.add(unloader.getRecordBatch()); - final String exceptionMessage = "java.lang.RuntimeException: Error occurred while getting next schema root.\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.next(ArrowVectorIterator.java:205)\n\tat com.oceanbase.external.jdbc.JdbcScanner.loadNextBatch(JdbcScanner.java:73)\n\tat org.apache.arrow.c.ArrayStreamExporter$ExportedArrayStreamPrivateData.getNext(ArrayStreamExporter.java:72)\nCaused by: java.lang.RuntimeException: Error occurred while consuming data.\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.consumeData(ArrowVectorIterator.java:127)\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.load(ArrowVectorIterator.java:178)\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.next(ArrowVectorIterator.java:198)\n\t... 2 more\nCaused by: java.lang.OutOfMemoryError: Java heap space\n"; + final String exceptionMessage = "Error occurred while getting next schema root.\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.next(ArrowVectorIterator.java:205)\n\tat com.oceanbase.external.jdbc.JdbcScanner.loadNextBatch(JdbcScanner.java:73)\n\tat org.apache.arrow.c.ArrayStreamExporter$ExportedArrayStreamPrivateData.getNext(ArrayStreamExporter.java:72)\nCaused by: java.lang.RuntimeException: Error occurred while consuming data.\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.consumeData(ArrowVectorIterator.java:127)\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.load(ArrowVectorIterator.java:178)\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.next(ArrowVectorIterator.java:198)\n\t... 2 more\nCaused by: java.lang.OutOfMemoryError: Java heap space\n"; - RuntimeException ex = new RuntimeException(exceptionMessage); - batches.add(ex); + RuntimeException exToThrow = new RuntimeException(exceptionMessage); + batches.add(exToThrow); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + exToThrow.printStackTrace(pw); + final String expectExceptionMessage = sw.toString(); ArrowReader source = new ExceptionMemoryArrowReader(allocator, schema, batches); @@ -73,6 +83,7 @@ public void testException() throws IOException { final VectorLoader loader = new VectorLoader(importRoot); Data.exportArrayStream(allocator, source, stream); + Throwable exceptionThrowed = null; try (final ArrowReader reader = Data.importArrayStream(allocator, stream)) { assertThat(reader.getVectorSchemaRoot().getSchema()).isEqualTo(schema); @@ -80,13 +91,12 @@ public void testException() throws IOException { try { reader.loadNextBatch(); } catch (Exception e) { - Throwable exToCheck = e; - while (exToCheck != null && !(exToCheck instanceof RuntimeException)) { - exToCheck = exToCheck.getCause(); - } - if (exToCheck != null) { - assertThat(exToCheck.getMessage()).isEqualTo(exceptionMessage); - } + assertThat(exceptionThrowed).isEqualTo(null); + final String eMessage = e.getMessage(); + assertThat(eMessage.length()).isGreaterThan(expectExceptionMessage.length() + 1); // 1 for '}' + assertThat(eMessage.substring(eMessage.length() - expectExceptionMessage.length() - 1, eMessage.length() - 1)) + .isEqualTo(expectExceptionMessage); + exceptionThrowed = e; continue; } loader.load((ArrowRecordBatch) batch); @@ -94,6 +104,7 @@ public void testException() throws IOException { assertThat(reader.getVectorSchemaRoot().getRowCount()).isEqualTo(root.getRowCount()); } } + assertThat(exceptionThrowed).isNotEqualTo(null); } } } From ee860b1caf5beff6043cb0901f7a3ecf3ab4a2ed Mon Sep 17 00:00:00 2001 From: wangyunlai Date: Mon, 2 Jun 2025 17:11:08 +0800 Subject: [PATCH 05/10] remove unused file --- c/ExceptionTest.java | 183 ------------------------------------------- 1 file changed, 183 deletions(-) delete mode 100644 c/ExceptionTest.java diff --git a/c/ExceptionTest.java b/c/ExceptionTest.java deleted file mode 100644 index 900d65e70f..0000000000 --- a/c/ExceptionTest.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.arrow.c; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Objects; -import java.util.function.Function; -import java.util.stream.Collectors; -import org.apache.arrow.c.jni.CDataJniException; -import org.apache.arrow.memory.BufferAllocator; -import org.apache.arrow.memory.RootAllocator; -import org.apache.arrow.vector.IntVector; -import org.apache.arrow.vector.VectorLoader; -import org.apache.arrow.vector.VectorSchemaRoot; -import org.apache.arrow.vector.VectorUnloader; -import org.apache.arrow.vector.dictionary.Dictionary; -import org.apache.arrow.vector.dictionary.DictionaryProvider; -import org.apache.arrow.vector.ipc.ArrowReader; -import org.apache.arrow.vector.ipc.message.ArrowRecordBatch; -import org.apache.arrow.vector.types.pojo.ArrowType; -import org.apache.arrow.vector.types.pojo.Field; -import org.apache.arrow.vector.types.pojo.Schema; -import org.junit.jupiter.api.Test; - -// Regression test for https://github.com/apache/arrow-java/issues/759 -final class ExceptionTest { - @Test - public void testException() throws IOException { - final Schema schema = - new Schema(Collections.singletonList(Field.nullable("ints", new ArrowType.Int(32, true)))); - final List batches = new ArrayList<>(); - - try (BufferAllocator allocator = new RootAllocator(); - VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) { - final IntVector ints = (IntVector) root.getVector(0); - VectorUnloader unloader = new VectorUnloader(root); - - root.allocateNew(); - ints.setSafe(0, 1); - ints.setSafe(1, 2); - ints.setSafe(2, 4); - ints.setSafe(3, 8); - root.setRowCount(4); - batches.add(unloader.getRecordBatch()); - - final String exceptionMessage = "Error occurred while getting next schema root.\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.next(ArrowVectorIterator.java:205)\n\tat com.oceanbase.external.jdbc.JdbcScanner.loadNextBatch(JdbcScanner.java:73)\n\tat org.apache.arrow.c.ArrayStreamExporter$ExportedArrayStreamPrivateData.getNext(ArrayStreamExporter.java:72)\nCaused by: java.lang.RuntimeException: Error occurred while consuming data.\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.consumeData(ArrowVectorIterator.java:127)\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.load(ArrowVectorIterator.java:178)\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.next(ArrowVectorIterator.java:198)\n\t... 2 more\nCaused by: java.lang.OutOfMemoryError: Java heap space\n"; - - RuntimeException exToThrow = new RuntimeException(exceptionMessage); - batches.add(exToThrow); - - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - exToThrow.printStackTrace(pw); - final String expectExceptionMessage = sw.toString(); - - ArrowReader source = new ExceptionMemoryArrowReader(allocator, schema, batches); - - try (final ArrowArrayStream stream = ArrowArrayStream.allocateNew(allocator); - final VectorSchemaRoot importRoot = VectorSchemaRoot.create(schema, allocator)) { - final VectorLoader loader = new VectorLoader(importRoot); - Data.exportArrayStream(allocator, source, stream); - - Throwable exceptionThrowed = null; - try (final ArrowReader reader = Data.importArrayStream(allocator, stream)) { - assertThat(reader.getVectorSchemaRoot().getSchema()).isEqualTo(schema); - - for (Object batch : batches) { - try { - reader.loadNextBatch(); - } catch (Exception e) { - assertThat(exceptionThrowed).isEqualTo(null); - final String eMessage = e.getMessage(); - assertThat(eMessage.length()).isGreaterThan(expectExceptionMessage.length() + 1); // 1 for '}' - assertThat(eMessage.substring(eMessage.length() - expectExceptionMessage.length() - 1, eMessage.length() - 1)) - .isEqualTo(expectExceptionMessage); - exceptionThrowed = e; - continue; - } - loader.load((ArrowRecordBatch) batch); - - assertThat(reader.getVectorSchemaRoot().getRowCount()).isEqualTo(root.getRowCount()); - } - } - assertThat(exceptionThrowed).isNotEqualTo(null); - } - } - } - - static class ExceptionMemoryArrowReader extends ArrowReader { - private final Schema schema; - private final List batches; // set ArrowRecordBatch or Exception - private final DictionaryProvider provider; - private int nextBatch; - - ExceptionMemoryArrowReader( - BufferAllocator allocator, - Schema schema, - List batches) { - super(allocator); - this.schema = schema; - this.batches = batches; - this.provider = new CDataDictionaryProvider(); - this.nextBatch = 0; - } - - @Override - public Dictionary lookup(long id) { - return provider.lookup(id); - } - - @Override - public Set getDictionaryIds() { - return provider.getDictionaryIds(); - } - - @Override - public Map getDictionaryVectors() { - return getDictionaryIds().stream() - .collect(Collectors.toMap(Function.identity(), this::lookup)); - } - - @Override - public boolean loadNextBatch() throws IOException { - if (nextBatch < batches.size()) { - Object object = batches.get(nextBatch++); - if (object instanceof RuntimeException) { - throw (RuntimeException) object; - } - VectorLoader loader = new VectorLoader(getVectorSchemaRoot()); - loader.load((ArrowRecordBatch) object); - return true; - } - return false; - } - - @Override - public long bytesRead() { - return 0; - } - - @Override - protected void closeReadSource() throws IOException { - try { - for (Object object : batches) { - if (object instanceof ArrowRecordBatch) { - ArrowRecordBatch batch = (ArrowRecordBatch) object; - batch.close(); - } - } - } catch (Exception e) { - throw new IOException(e); - } - } - - @Override - protected Schema readSchema() { - return schema; - } - } -} From 88ed36bc84ee16e02d3edc9e08a0924a56ab3fdd Mon Sep 17 00:00:00 2001 From: wangyunlai Date: Mon, 2 Jun 2025 17:13:43 +0800 Subject: [PATCH 06/10] shorten exception message --- c/src/test/java/org/apache/arrow/c/ExceptionTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java index 900d65e70f..8cb77df99a 100644 --- a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java +++ b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java @@ -66,7 +66,7 @@ public void testException() throws IOException { root.setRowCount(4); batches.add(unloader.getRecordBatch()); - final String exceptionMessage = "Error occurred while getting next schema root.\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.next(ArrowVectorIterator.java:205)\n\tat com.oceanbase.external.jdbc.JdbcScanner.loadNextBatch(JdbcScanner.java:73)\n\tat org.apache.arrow.c.ArrayStreamExporter$ExportedArrayStreamPrivateData.getNext(ArrayStreamExporter.java:72)\nCaused by: java.lang.RuntimeException: Error occurred while consuming data.\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.consumeData(ArrowVectorIterator.java:127)\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.load(ArrowVectorIterator.java:178)\n\tat org.apache.arrow.adapter.jdbc.ArrowVectorIterator.next(ArrowVectorIterator.java:198)\n\t... 2 more\nCaused by: java.lang.OutOfMemoryError: Java heap space\n"; + final String exceptionMessage = "This is a message for testing exception."; RuntimeException exToThrow = new RuntimeException(exceptionMessage); batches.add(exToThrow); @@ -93,7 +93,8 @@ public void testException() throws IOException { } catch (Exception e) { assertThat(exceptionThrowed).isEqualTo(null); final String eMessage = e.getMessage(); - assertThat(eMessage.length()).isGreaterThan(expectExceptionMessage.length() + 1); // 1 for '}' + // 1 for '}', ref to CDataJniException + assertThat(eMessage.length()).isGreaterThan(expectExceptionMessage.length() + 1); assertThat(eMessage.substring(eMessage.length() - expectExceptionMessage.length() - 1, eMessage.length() - 1)) .isEqualTo(expectExceptionMessage); exceptionThrowed = e; From 3f4071137b050d509d45520d99d49d57daaf380d Mon Sep 17 00:00:00 2001 From: wangyunlai Date: Sat, 7 Jun 2025 20:35:38 +0800 Subject: [PATCH 07/10] assert throws --- .../org/apache/arrow/c/ExceptionTest.java | 35 +++---------------- 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java index 8cb77df99a..33f2ce0912 100644 --- a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java +++ b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java @@ -17,6 +17,7 @@ package org.apache.arrow.c; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowableOfType; import java.io.IOException; import java.io.PrintWriter; @@ -55,16 +56,6 @@ public void testException() throws IOException { try (BufferAllocator allocator = new RootAllocator(); VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) { - final IntVector ints = (IntVector) root.getVector(0); - VectorUnloader unloader = new VectorUnloader(root); - - root.allocateNew(); - ints.setSafe(0, 1); - ints.setSafe(1, 2); - ints.setSafe(2, 4); - ints.setSafe(3, 8); - root.setRowCount(4); - batches.add(unloader.getRecordBatch()); final String exceptionMessage = "This is a message for testing exception."; @@ -83,29 +74,13 @@ public void testException() throws IOException { final VectorLoader loader = new VectorLoader(importRoot); Data.exportArrayStream(allocator, source, stream); - Throwable exceptionThrowed = null; try (final ArrowReader reader = Data.importArrayStream(allocator, stream)) { - assertThat(reader.getVectorSchemaRoot().getSchema()).isEqualTo(schema); - - for (Object batch : batches) { - try { - reader.loadNextBatch(); - } catch (Exception e) { - assertThat(exceptionThrowed).isEqualTo(null); - final String eMessage = e.getMessage(); - // 1 for '}', ref to CDataJniException - assertThat(eMessage.length()).isGreaterThan(expectExceptionMessage.length() + 1); - assertThat(eMessage.substring(eMessage.length() - expectExceptionMessage.length() - 1, eMessage.length() - 1)) + IOException jniException = catchThrowableOfType(IOException.class, reader::loadNextBatch); + final String jniMessage = jniException.getMessage(); + assertThat(jniMessage.length()).isGreaterThan(expectExceptionMessage.length() + 1); // 1 for '}' + assertThat(jniMessage.substring(jniMessage.length() - expectExceptionMessage.length() - 1, jniMessage.length() - 1)) .isEqualTo(expectExceptionMessage); - exceptionThrowed = e; - continue; - } - loader.load((ArrowRecordBatch) batch); - - assertThat(reader.getVectorSchemaRoot().getRowCount()).isEqualTo(root.getRowCount()); - } } - assertThat(exceptionThrowed).isNotEqualTo(null); } } } From cd9cd5b1e99ba465872c3f2d49e6568a6754b006 Mon Sep 17 00:00:00 2001 From: wangyunlai Date: Sun, 8 Jun 2025 20:03:13 +0800 Subject: [PATCH 08/10] simplify message test --- c/src/test/java/org/apache/arrow/c/ExceptionTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java index 33f2ce0912..a85a367b9f 100644 --- a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java +++ b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java @@ -77,9 +77,7 @@ public void testException() throws IOException { try (final ArrowReader reader = Data.importArrayStream(allocator, stream)) { IOException jniException = catchThrowableOfType(IOException.class, reader::loadNextBatch); final String jniMessage = jniException.getMessage(); - assertThat(jniMessage.length()).isGreaterThan(expectExceptionMessage.length() + 1); // 1 for '}' - assertThat(jniMessage.substring(jniMessage.length() - expectExceptionMessage.length() - 1, jniMessage.length() - 1)) - .isEqualTo(expectExceptionMessage); + assertThat(jniMessage.endsWith(expectExceptionMessage + "}")); } } } From d6ea33e609c5c3997816da1d0fa2f7fd76869374 Mon Sep 17 00:00:00 2001 From: wangyunlai Date: Mon, 30 Jun 2025 11:17:27 +0800 Subject: [PATCH 09/10] fix checkstyle --- c/src/test/java/org/apache/arrow/c/ExceptionTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java index a85a367b9f..fdbb84e07d 100644 --- a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java +++ b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java @@ -27,16 +27,12 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; -import org.apache.arrow.c.jni.CDataJniException; import org.apache.arrow.memory.BufferAllocator; import org.apache.arrow.memory.RootAllocator; -import org.apache.arrow.vector.IntVector; import org.apache.arrow.vector.VectorLoader; import org.apache.arrow.vector.VectorSchemaRoot; -import org.apache.arrow.vector.VectorUnloader; import org.apache.arrow.vector.dictionary.Dictionary; import org.apache.arrow.vector.dictionary.DictionaryProvider; import org.apache.arrow.vector.ipc.ArrowReader; From 2d715828adb292e25a98ab87f630e6506c32c26e Mon Sep 17 00:00:00 2001 From: wangyunlai Date: Mon, 30 Jun 2025 12:02:12 +0800 Subject: [PATCH 10/10] fix spotless --- c/src/test/java/org/apache/arrow/c/ExceptionTest.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java index fdbb84e07d..5bc96a8f99 100644 --- a/c/src/test/java/org/apache/arrow/c/ExceptionTest.java +++ b/c/src/test/java/org/apache/arrow/c/ExceptionTest.java @@ -51,7 +51,7 @@ public void testException() throws IOException { final List batches = new ArrayList<>(); try (BufferAllocator allocator = new RootAllocator(); - VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) { + VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) { final String exceptionMessage = "This is a message for testing exception."; @@ -66,7 +66,7 @@ public void testException() throws IOException { ArrowReader source = new ExceptionMemoryArrowReader(allocator, schema, batches); try (final ArrowArrayStream stream = ArrowArrayStream.allocateNew(allocator); - final VectorSchemaRoot importRoot = VectorSchemaRoot.create(schema, allocator)) { + final VectorSchemaRoot importRoot = VectorSchemaRoot.create(schema, allocator)) { final VectorLoader loader = new VectorLoader(importRoot); Data.exportArrayStream(allocator, source, stream); @@ -85,10 +85,7 @@ static class ExceptionMemoryArrowReader extends ArrowReader { private final DictionaryProvider provider; private int nextBatch; - ExceptionMemoryArrowReader( - BufferAllocator allocator, - Schema schema, - List batches) { + ExceptionMemoryArrowReader(BufferAllocator allocator, Schema schema, List batches) { super(allocator); this.schema = schema; this.batches = batches;