This is an automated email from the ASF dual-hosted git repository.
paleolimbot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-nanoarrow.git
The following commit(s) were added to refs/heads/main by this push:
new 15019d6 Add more C++ and error handling helpers (#189)
15019d6 is described below
commit 15019d6680367835a8082e847468ed0f9d3b7530
Author: Dewey Dunnington <[email protected]>
AuthorDate: Tue May 9 09:46:19 2023 -0400
Add more C++ and error handling helpers (#189)
This PR is motivated by reading some early usage of nanoarrow (including
some of my own!). Basically, I just want to make it really easy to
communicate failed calls to the nanoarrow API, particularly for those
not used to C-style return code error handling. This PR:
- Adds `NANOARROW_THROW_NOT_OK()` in the C++ helper
- Adds `NANOARROW_ASSERT_OK()` in the C header
- Adds `NANOARROW_DEBUG` mode and inserts some more information when
compiled with this define
- Adds C++ helpers for the IPC extension to mirror the C API helpers
In writing and documenting the helpers, I solidifed some
guarantees...notably, that `ArrowError` has a nul-terminated error
message whenever an error code is returned.
---
.github/workflows/build-and-test.yaml | 5 +-
.github/workflows/packaging.yaml | 5 --
CMakeLists.txt | 3 +
docs/source/cpp.rst | 6 ++
docs/source/ipc.rst | 10 +++
extensions/nanoarrow_ipc/CMakeLists.txt | 5 ++
.../nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc.hpp | 85 ++++++++++++++++++++++
.../src/nanoarrow/nanoarrow_ipc_decoder.c | 18 +++--
.../src/nanoarrow/nanoarrow_ipc_hpp_test.cc | 46 ++++++++++++
.../src/nanoarrow/nanoarrow_ipc_reader.c | 20 +++--
src/nanoarrow/array.c | 6 +-
src/nanoarrow/nanoarrow.h | 27 ++++++-
src/nanoarrow/nanoarrow.hpp | 50 +++++++++++++
src/nanoarrow/nanoarrow_hpp_test.cc | 10 +++
src/nanoarrow/nanoarrow_types.h | 67 +++++++++++++++++
src/nanoarrow/utils.c | 8 +-
src/nanoarrow/utils_test.cc | 29 +++++++-
17 files changed, 371 insertions(+), 29 deletions(-)
diff --git a/.github/workflows/build-and-test.yaml
b/.github/workflows/build-and-test.yaml
index b3857fc..abe54e4 100644
--- a/.github/workflows/build-and-test.yaml
+++ b/.github/workflows/build-and-test.yaml
@@ -40,7 +40,8 @@ jobs:
fail-fast: false
matrix:
config:
- - {label: default-build}
+ - {label: default-build, cmake_args: "-DCMAKE_BUILD_TYPE=Debug"}
+ - {label: release-build}
- {label: namespaced-build, cmake_args:
"-DNANOARROW_NAMESPACE=SomeUserNamespace"}
- {label: bundled-build, cmake_args: "-DNANOARROW_BUNDLE=ON"}
- {label: bundled-cpp-build, cmake_args: "-DNANOARROW_BUNDLE=ON
-DNANOARROW_BUNDLE_AS_CPP=ON"}
@@ -67,7 +68,7 @@ jobs:
sudo ldconfig
mkdir build
cd build
- cmake .. -DCMAKE_BUILD_TYPE=Debug -DNANOARROW_BUILD_TESTS=ON ${{
matrix.config.cmake_args }}
+ cmake .. -DNANOARROW_BUILD_TESTS=ON ${{ matrix.config.cmake_args }}
cmake --build .
- name: Check for non-namespaced symbols in namespaced build
diff --git a/.github/workflows/packaging.yaml b/.github/workflows/packaging.yaml
index 85a30ed..a778c96 100644
--- a/.github/workflows/packaging.yaml
+++ b/.github/workflows/packaging.yaml
@@ -127,11 +127,6 @@ jobs:
docker compose run --rm docs
popd
- - name: Documentation build logs
- if: always()
- run: |
- cat nanoarrow/docs/_build/build-docs.log
-
- name: Compress docs
shell: bash
run: |
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 01b96dc..4a72f2c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -91,6 +91,7 @@ if(NANOARROW_BUNDLE)
if(NANOARROW_BUILD_TESTS)
include_directories(${CMAKE_BINARY_DIR}/amalgamation)
add_library(nanoarrow ${NANOARROW_C_TEMP})
+ target_compile_definitions(nanoarrow PUBLIC
"$<$<CONFIG:Debug>:NANOARROW_DEBUG>")
endif()
# Install the amalgamated header and source
@@ -107,6 +108,8 @@ else()
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
$<INSTALL_INTERFACE:include>)
target_include_directories(nanoarrow PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/generated>)
+ target_compile_definitions(nanoarrow PUBLIC
"$<$<CONFIG:Debug>:NANOARROW_DEBUG>")
+
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
target_compile_options(
diff --git a/docs/source/cpp.rst b/docs/source/cpp.rst
index 44b7b4e..615262f 100644
--- a/docs/source/cpp.rst
+++ b/docs/source/cpp.rst
@@ -20,6 +20,12 @@ C++ API Reference
.. doxygengroup:: nanoarrow_hpp
+Error handling
+--------------
+
+.. doxygengroup:: nanoarrow_hpp-errors
+ :members:
+
Owning object wrappers
----------------------
.. doxygengroup:: nanoarrow_hpp-unique
diff --git a/docs/source/ipc.rst b/docs/source/ipc.rst
index 8bc7214..fb009c1 100644
--- a/docs/source/ipc.rst
+++ b/docs/source/ipc.rst
@@ -18,6 +18,16 @@
IPC Extension Reference
=======================
+C API
+------------------------
+
.. doxygengroup:: nanoarrow_ipc
:project: nanoarrow_ipc
:members:
+
+C++ Helpers
+------------------------
+
+.. doxygengroup:: nanoarrow_ipc_hpp-unique
+ :project: nanoarrow_ipc
+ :members:
diff --git a/extensions/nanoarrow_ipc/CMakeLists.txt
b/extensions/nanoarrow_ipc/CMakeLists.txt
index c0b335c..92a85fa 100644
--- a/extensions/nanoarrow_ipc/CMakeLists.txt
+++ b/extensions/nanoarrow_ipc/CMakeLists.txt
@@ -212,6 +212,7 @@ if (NANOARROW_IPC_BUILD_TESTS)
add_executable(nanoarrow_ipc_decoder_test
src/nanoarrow/nanoarrow_ipc_decoder_test.cc)
add_executable(nanoarrow_ipc_reader_test
src/nanoarrow/nanoarrow_ipc_reader_test.cc)
add_executable(nanoarrow_ipc_files_test
src/nanoarrow/nanoarrow_ipc_files_test.cc)
+ add_executable(nanoarrow_ipc_hpp_test
src/nanoarrow/nanoarrow_ipc_hpp_test.cc)
if(NANOARROW_IPC_CODE_COVERAGE)
target_compile_options(ipc_coverage_config INTERFACE -O0 -g --coverage)
@@ -236,11 +237,15 @@ if (NANOARROW_IPC_BUILD_TESTS)
target_link_libraries(
nanoarrow_ipc_files_test
nanoarrow_ipc nanoarrow ${NANOARROW_IPC_ARROW_TARGET} gtest_main
ipc_coverage_config)
+ target_link_libraries(
+ nanoarrow_ipc_hpp_test
+ nanoarrow_ipc nanoarrow ${NANOARROW_IPC_ARROW_TARGET} gtest_main
ipc_coverage_config)
include(GoogleTest)
gtest_discover_tests(nanoarrow_ipc_decoder_test)
gtest_discover_tests(nanoarrow_ipc_reader_test)
gtest_discover_tests(nanoarrow_ipc_files_test)
+ gtest_discover_tests(nanoarrow_ipc_hpp_test)
endif()
if (NANOARROW_IPC_BUILD_APPS)
diff --git a/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc.hpp
b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc.hpp
new file mode 100644
index 0000000..505f827
--- /dev/null
+++ b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc.hpp
@@ -0,0 +1,85 @@
+// 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.
+
+#include "nanoarrow_ipc.h"
+
+#ifndef NANOARROW_IPC_HPP_INCLUDED
+#define NANOARROW_IPC_HPP_INCLUDED
+
+namespace nanoarrow {
+
+namespace internal {
+
+static inline void init_pointer(struct ArrowIpcDecoder* data) {
+ data->private_data = nullptr;
+}
+
+static inline void move_pointer(struct ArrowIpcDecoder* src,
+ struct ArrowIpcDecoder* dst) {
+ memcpy(dst, src, sizeof(struct ArrowIpcDecoder));
+ src->private_data = nullptr;
+}
+
+static inline void release_pointer(struct ArrowIpcDecoder* data) {
+ ArrowIpcDecoderReset(data);
+}
+
+static inline void init_pointer(struct ArrowIpcInputStream* data) {
+ data->release = nullptr;
+}
+
+static inline void move_pointer(struct ArrowIpcInputStream* src,
+ struct ArrowIpcInputStream* dst) {
+ memcpy(dst, src, sizeof(struct ArrowIpcInputStream));
+ src->release = nullptr;
+}
+
+static inline void release_pointer(struct ArrowIpcInputStream* data) {
+ if (data->release != nullptr) {
+ data->release(data);
+ }
+}
+
+} // namespace internal
+} // namespace nanoarrow
+
+#include "nanoarrow.hpp"
+
+namespace nanoarrow {
+
+namespace ipc {
+
+/// \defgroup nanoarrow_ipc_hpp-unique Unique object wrappers
+///
+/// Extends the unique object wrappers in nanoarrow.hpp to include C structs
+/// defined in the nanoarrow_ipc.h header.
+///
+/// @{
+
+/// \brief Class wrapping a unique struct ArrowIpcDecoder
+using UniqueDecoder = internal::Unique<struct ArrowIpcDecoder>;
+
+/// \brief Class wrapping a unique struct ArrowIpcInputStream
+using UniqueInputStream = internal::Unique<struct ArrowIpcInputStream>;
+
+/// @}
+
+} // namespace ipc
+
+} // namespace nanoarrow
+
+#endif
diff --git a/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_decoder.c
b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_decoder.c
index 1d78e96..e401cac 100644
--- a/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_decoder.c
+++ b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_decoder.c
@@ -212,15 +212,17 @@ void ArrowIpcDecoderReset(struct ArrowIpcDecoder*
decoder) {
struct ArrowIpcDecoderPrivate* private_data =
(struct ArrowIpcDecoderPrivate*)decoder->private_data;
- ArrowArrayViewReset(&private_data->array_view);
+ if (private_data != NULL) {
+ ArrowArrayViewReset(&private_data->array_view);
- if (private_data->fields != NULL) {
- ArrowFree(private_data->fields);
- private_data->n_fields = 0;
- }
+ if (private_data->fields != NULL) {
+ ArrowFree(private_data->fields);
+ private_data->n_fields = 0;
+ }
- ArrowFree(private_data);
- memset(decoder, 0, sizeof(struct ArrowIpcDecoder));
+ ArrowFree(private_data);
+ memset(decoder, 0, sizeof(struct ArrowIpcDecoder));
+ }
}
static inline uint32_t ArrowIpcReadContinuationBytes(struct ArrowBufferView*
data) {
@@ -1222,7 +1224,7 @@ static ArrowErrorCode ArrowIpcMakeBufferFromView(struct
ArrowIpcBufferFactory* f
view.size_bytes = src->buffer_length_bytes;
ArrowBufferInit(dst);
- NANOARROW_RETURN_NOT_OK(ArrowBufferAppendBufferView(dst, view));
+ NANOARROW_RETURN_NOT_OK_WITH_ERROR(ArrowBufferAppendBufferView(dst, view),
error);
return NANOARROW_OK;
}
diff --git a/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_hpp_test.cc
b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_hpp_test.cc
new file mode 100644
index 0000000..38edb8c
--- /dev/null
+++ b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_hpp_test.cc
@@ -0,0 +1,46 @@
+// 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.
+
+#include <gtest/gtest.h>
+
+#include "nanoarrow_ipc.hpp"
+
+TEST(NanoarrowIpcHppTest, NanoarrowIpcHppTestUniqueDecoder) {
+ nanoarrow::ipc::UniqueDecoder decoder;
+
+ EXPECT_EQ(decoder->private_data, nullptr);
+ ASSERT_EQ(ArrowIpcDecoderInit(decoder.get()), NANOARROW_OK);
+ EXPECT_NE(decoder->private_data, nullptr);
+
+ nanoarrow::ipc::UniqueDecoder decoder2 = std::move(decoder);
+ EXPECT_NE(decoder2->private_data, nullptr);
+ EXPECT_EQ(decoder->private_data, nullptr);
+}
+
+TEST(NanoarrowIpcHppTest, NanoarrowIpcHppTestUniqueInputStream) {
+ nanoarrow::ipc::UniqueInputStream input;
+ nanoarrow::UniqueBuffer buf;
+ ASSERT_EQ(ArrowBufferAppend(buf.get(), "abcdefg", 7), NANOARROW_OK);
+
+ EXPECT_EQ(input->release, nullptr);
+ ASSERT_EQ(ArrowIpcInputStreamInitBuffer(input.get(), buf.get()),
NANOARROW_OK);
+ EXPECT_NE(input->release, nullptr);
+
+ nanoarrow::ipc::UniqueInputStream input2 = std::move(input);
+ EXPECT_NE(input2->release, nullptr);
+ EXPECT_EQ(input->release, nullptr);
+}
diff --git a/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_reader.c
b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_reader.c
index 0e457c7..0e72b3d 100644
--- a/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_reader.c
+++ b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_reader.c
@@ -202,7 +202,8 @@ static int ArrowIpcArrayStreamReaderNextHeader(
int64_t bytes_read = 0;
// Read 8 bytes (continuation + header size in bytes)
- NANOARROW_RETURN_NOT_OK(ArrowBufferReserve(&private_data->header, 8));
+ NANOARROW_RETURN_NOT_OK_WITH_ERROR(ArrowBufferReserve(&private_data->header,
8),
+ &private_data->error);
NANOARROW_RETURN_NOT_OK(private_data->input.read(&private_data->input,
private_data->header.data,
8,
&bytes_read,
&private_data->error));
@@ -233,8 +234,9 @@ static int ArrowIpcArrayStreamReaderNextHeader(
// Read the header bytes
int64_t expected_header_bytes = private_data->decoder.header_size_bytes - 8;
- NANOARROW_RETURN_NOT_OK(
- ArrowBufferReserve(&private_data->header, expected_header_bytes));
+ NANOARROW_RETURN_NOT_OK_WITH_ERROR(
+ ArrowBufferReserve(&private_data->header, expected_header_bytes),
+ &private_data->error);
NANOARROW_RETURN_NOT_OK(
private_data->input.read(&private_data->input, private_data->header.data
+ 8,
expected_header_bytes, &bytes_read,
&private_data->error));
@@ -264,7 +266,8 @@ static int ArrowIpcArrayStreamReaderNextBody(
// Read the body bytes
private_data->body.size_bytes = 0;
- NANOARROW_RETURN_NOT_OK(ArrowBufferReserve(&private_data->body,
bytes_to_read));
+ NANOARROW_RETURN_NOT_OK_WITH_ERROR(
+ ArrowBufferReserve(&private_data->body, bytes_to_read),
&private_data->error);
NANOARROW_RETURN_NOT_OK(private_data->input.read(&private_data->input,
private_data->body.data,
bytes_to_read,
&bytes_read,
&private_data->error));
@@ -304,8 +307,10 @@ static int ArrowIpcArrayStreamReaderReadSchemaIfNeeded(
}
// Notify the decoder of buffer endianness
- NANOARROW_RETURN_NOT_OK(ArrowIpcDecoderSetEndianness(&private_data->decoder,
-
private_data->decoder.endianness));
+ NANOARROW_RETURN_NOT_OK_WITH_ERROR(
+ ArrowIpcDecoderSetEndianness(&private_data->decoder,
+ private_data->decoder.endianness),
+ &private_data->error);
struct ArrowSchema tmp;
NANOARROW_RETURN_NOT_OK(
@@ -369,7 +374,8 @@ static int ArrowIpcArrayStreamReaderGetNext(struct
ArrowArrayStream* stream,
if (private_data->use_shared_buffers) {
struct ArrowIpcSharedBuffer shared;
- NANOARROW_RETURN_NOT_OK(ArrowIpcSharedBufferInit(&shared,
&private_data->body));
+ NANOARROW_RETURN_NOT_OK_WITH_ERROR(
+ ArrowIpcSharedBufferInit(&shared, &private_data->body),
&private_data->error);
NANOARROW_RETURN_NOT_OK(ArrowIpcDecoderDecodeArrayFromShared(
&private_data->decoder, &shared, private_data->field_index, &tmp,
&private_data->error));
diff --git a/src/nanoarrow/array.c b/src/nanoarrow/array.c
index ae1db4d..403b4b0 100644
--- a/src/nanoarrow/array.c
+++ b/src/nanoarrow/array.c
@@ -441,7 +441,7 @@ ArrowErrorCode ArrowArrayFinishBuilding(struct ArrowArray*
array,
// in some implementations (at least one version of Arrow C++ at the time
this
// was added). Only do this fix if we can assume CPU data access.
if (validation_level >= NANOARROW_VALIDATION_LEVEL_DEFAULT) {
- NANOARROW_RETURN_NOT_OK(ArrowArrayFinalizeBuffers(array));
+ NANOARROW_RETURN_NOT_OK_WITH_ERROR(ArrowArrayFinalizeBuffers(array),
error);
}
// Make sure the value we get with array->buffers[i] is set to the actual
@@ -456,7 +456,8 @@ ArrowErrorCode ArrowArrayFinishBuilding(struct ArrowArray*
array,
// into the wild that is going to segfault
struct ArrowArrayView array_view;
- NANOARROW_RETURN_NOT_OK(ArrowArrayViewInitFromArray(&array_view, array));
+ NANOARROW_RETURN_NOT_OK_WITH_ERROR(ArrowArrayViewInitFromArray(&array_view,
array),
+ error);
// Check buffer sizes once without using internal buffer data since
// ArrowArrayViewSetArray() assumes that all the buffers are long enough
@@ -550,6 +551,7 @@ ArrowErrorCode ArrowArrayViewInitFromSchema(struct
ArrowArrayView* array_view,
result = ArrowArrayViewAllocateChildren(array_view, schema->n_children);
if (result != NANOARROW_OK) {
+ ArrowErrorSet(error, "ArrowArrayViewAllocateChildren() failed");
ArrowArrayViewReset(array_view);
return result;
}
diff --git a/src/nanoarrow/nanoarrow.h b/src/nanoarrow/nanoarrow.h
index 09ee85d..7b11825 100644
--- a/src/nanoarrow/nanoarrow.h
+++ b/src/nanoarrow/nanoarrow.h
@@ -182,7 +182,16 @@ struct ArrowBufferAllocator ArrowBufferDeallocator(
/// need to communicate more verbose error information accept a pointer
/// to an ArrowError. This can be stack or statically allocated. The
/// content of the message is undefined unless an error code has been
-/// returned.
+/// returned. If a nanoarrow function is passed a non-null ArrowError pointer,
the
+/// ArrowError pointed to by the argument will be propagated with a
+/// null-terminated error message. It is safe to pass a NULL ArrowError
anywhere
+/// in the nanoarrow API.
+///
+/// Except where documented, it is generally not safe to continue after a
+/// function has returned a non-zero ArrowErrorCode. The
NANOARROW_RETURN_NOT_OK and
+/// NANOARROW_ASSERT_OK macros are provided to help propagate errors. C++
clients can use
+/// the helpers provided in the nanoarrow.hpp header to facilitate using C++
idioms
+/// for memory management and error propgagtion.
///
/// @{
@@ -192,10 +201,24 @@ struct ArrowError {
char message[1024];
};
-/// \brief Set the contents of an error using printf syntax
+/// \brief Ensure an ArrowError is null-terminated by zeroing the first
character.
+///
+/// If error is NULL, this function does nothing.
+static inline void ArrowErrorInit(struct ArrowError* error) {
+ if (error) {
+ error->message[0] = '\0';
+ }
+}
+
+/// \brief Set the contents of an error using printf syntax.
+///
+/// If error is NULL, this function does nothing and returns NANOARROW_OK.
ArrowErrorCode ArrowErrorSet(struct ArrowError* error, const char* fmt, ...);
/// \brief Get the contents of an error
+///
+/// If error is NULL, returns "", or returns the contents of the error message
+/// otherwise.
const char* ArrowErrorMessage(struct ArrowError* error);
/// @}
diff --git a/src/nanoarrow/nanoarrow.hpp b/src/nanoarrow/nanoarrow.hpp
index b01d2a6..e6adb3a 100644
--- a/src/nanoarrow/nanoarrow.hpp
+++ b/src/nanoarrow/nanoarrow.hpp
@@ -15,6 +15,7 @@
// specific language governing permissions and limitations
// under the License.
+#include <stdexcept>
#include <vector>
#include "nanoarrow.h"
@@ -31,6 +32,55 @@
namespace nanoarrow {
+/// \defgroup nanoarrow_hpp-errors Error handling helpers
+///
+/// Most functions in the C API return an ArrowErrorCode to communicate
+/// possible failure. Except where documented, it is usually not safe to
+/// continue after a non-zero value has been returned. While the
+/// nanoarrow C++ helpers do not throw any exceptions of their own,
+/// these helpers are provided to facilitate using the nanoarrow C++ helpers
+/// in frameworks where this is a useful error handling idiom.
+///
+/// @{
+
+class Exception : public std::exception {
+ public:
+ Exception(const std::string& msg) : msg_(msg) {}
+ const char* what() const noexcept { return msg_.c_str(); }
+
+ private:
+ std::string msg_;
+};
+
+#if defined(NANOARROW_DEBUG)
+#define _NANOARROW_THROW_NOT_OK_IMPL(NAME, EXPR, EXPR_STR)
\
+ do {
\
+ const int NAME = (EXPR);
\
+ if (NAME) {
\
+ throw nanoarrow::Exception(
\
+ std::string(EXPR_STR) + std::string(" failed with errno ") +
\
+ std::to_string(NAME) + std::string("\n * ") + std::string(__FILE__)
+ \
+ std::string(":") + std::to_string(__LINE__) + std::string("\n"));
\
+ }
\
+ } while (0)
+#else
+#define _NANOARROW_THROW_NOT_OK_IMPL(NAME, EXPR, EXPR_STR) \
+ do { \
+ const int NAME = (EXPR); \
+ if (NAME) { \
+ throw nanoarrow::Exception(std::string(EXPR_STR) + \
+ std::string(" failed with errno ") + \
+ std::to_string(NAME)); \
+ } \
+ } while (0)
+#endif
+
+#define NANOARROW_THROW_NOT_OK(EXPR)
\
+ _NANOARROW_THROW_NOT_OK_IMPL(_NANOARROW_MAKE_NAME(errno_status_,
__COUNTER__), EXPR, \
+ #EXPR)
+
+/// @}
+
namespace internal {
/// \defgroup nanoarrow_hpp-unique_base Base classes for Unique wrappers
diff --git a/src/nanoarrow/nanoarrow_hpp_test.cc
b/src/nanoarrow/nanoarrow_hpp_test.cc
index bd5393a..4b1d661 100644
--- a/src/nanoarrow/nanoarrow_hpp_test.cc
+++ b/src/nanoarrow/nanoarrow_hpp_test.cc
@@ -19,6 +19,16 @@
#include "nanoarrow/nanoarrow.hpp"
+TEST(NanoarrowHppTest, NanoarrowHppExceptionTest) {
+ ASSERT_THROW(NANOARROW_THROW_NOT_OK(EINVAL), nanoarrow::Exception);
+ ASSERT_NO_THROW(NANOARROW_THROW_NOT_OK(NANOARROW_OK));
+ try {
+ NANOARROW_THROW_NOT_OK(EINVAL);
+ } catch (const nanoarrow::Exception& e) {
+ EXPECT_EQ(std::string(e.what()).substr(0, 24), "EINVAL failed with errno");
+ }
+}
+
TEST(NanoarrowHppTest, NanoarrowHppUniqueArrayTest) {
nanoarrow::UniqueArray array;
EXPECT_EQ(array->release, nullptr);
diff --git a/src/nanoarrow/nanoarrow_types.h b/src/nanoarrow/nanoarrow_types.h
index ee380de..58582ab 100644
--- a/src/nanoarrow/nanoarrow_types.h
+++ b/src/nanoarrow/nanoarrow_types.h
@@ -23,6 +23,11 @@
#include "nanoarrow_config.h"
+#if defined(NANOARROW_DEBUG) && !defined(NANOARROW_PRINT_AND_DIE)
+#include <stdio.h>
+#include <stdlib.h>
+#endif
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -159,6 +164,27 @@ static inline void ArrowArrayStreamMove(struct
ArrowArrayStream* src,
#define _NANOARROW_CHECK_RANGE(x_, min_, max_) \
NANOARROW_RETURN_NOT_OK((x_ >= min_ && x_ <= max_) ? NANOARROW_OK : EINVAL)
+#if defined(NANOARROW_DEBUG)
+#define _NANOARROW_RETURN_NOT_OK_WITH_ERROR_IMPL(NAME, EXPR, ERROR_PTR_EXPR,
EXPR_STR) \
+ do {
\
+ const int NAME = (EXPR);
\
+ if (NAME) {
\
+ ArrowErrorSet((ERROR_PTR_EXPR), "%s failed with errno %d\n* %s:%d",
EXPR_STR, \
+ NAME, __FILE__, __LINE__);
\
+ return NAME;
\
+ }
\
+ } while (0)
+#else
+#define _NANOARROW_RETURN_NOT_OK_WITH_ERROR_IMPL(NAME, EXPR, ERROR_PTR_EXPR,
EXPR_STR) \
+ do {
\
+ const int NAME = (EXPR);
\
+ if (NAME) {
\
+ ArrowErrorSet((ERROR_PTR_EXPR), "%s failed with errno %d", EXPR_STR,
NAME); \
+ return NAME;
\
+ }
\
+ } while (0)
+#endif
+
/// \brief Return code for success.
/// \ingroup nanoarrow-errors
#define NANOARROW_OK 0
@@ -172,6 +198,47 @@ typedef int ArrowErrorCode;
#define NANOARROW_RETURN_NOT_OK(EXPR) \
_NANOARROW_RETURN_NOT_OK_IMPL(_NANOARROW_MAKE_NAME(errno_status_,
__COUNTER__), EXPR)
+/// \brief Check the result of an expression and return it if not NANOARROW_OK,
+/// adding an auto-generated message to an ArrowError.
+/// \ingroup nanoarrow-errors
+///
+/// This macro is used to ensure that functions that accept an ArrowError
+/// as input always set its message when returning an error code (e.g., when
calling
+/// a nanoarrow function that does *not* accept ArrowError).
+#define NANOARROW_RETURN_NOT_OK_WITH_ERROR(EXPR, ERROR_EXPR) \
+ _NANOARROW_RETURN_NOT_OK_WITH_ERROR_IMPL( \
+ _NANOARROW_MAKE_NAME(errno_status_, __COUNTER__), EXPR, ERROR_EXPR,
#EXPR)
+
+#if defined(NANOARROW_DEBUG) && !defined(NANOARROW_PRINT_AND_DIE)
+#define NANOARROW_PRINT_AND_DIE(VALUE, EXPR_STR)
\
+ do {
\
+ fprintf(stderr, "%s failed with errno %d\n* %s:%d\n", EXPR_STR,
(int)(VALUE), \
+ __FILE__, (int)__LINE__);
\
+ abort();
\
+ } while (0)
+#endif
+
+#if defined(NANOARROW_DEBUG)
+#define _NANOARROW_ASSERT_OK_IMPL(NAME, EXPR, EXPR_STR) \
+ do { \
+ const int NAME = (EXPR); \
+ if (NAME) NANOARROW_PRINT_AND_DIE(NAME, EXPR_STR); \
+ } while (0)
+
+/// \brief Assert that an expression's value is NANOARROW_OK
+/// \ingroup nanoarrow-errors
+///
+/// If nanoarrow was built in debug mode (i.e., defined(NANOARROW_DEBUG) is
true),
+/// print a message to stderr and abort. If nanoarrow was bulit in release
mode,
+/// this statement has no effect. You can customize fatal error behaviour
+/// be defining the NANOARROW_PRINT_AND_DIE macro before including nanoarrow.h
+/// This macro is provided as a convenience for users and is not used
internally.
+#define NANOARROW_ASSERT_OK(EXPR) \
+ _NANOARROW_ASSERT_OK_IMPL(_NANOARROW_MAKE_NAME(errno_status_, __COUNTER__),
EXPR, #EXPR)
+#else
+#define NANOARROW_ASSERT_OK(EXPR) EXPR
+#endif
+
static char _ArrowIsLittleEndian(void) {
uint32_t check = 1;
char first_byte;
diff --git a/src/nanoarrow/utils.c b/src/nanoarrow/utils.c
index b87c1c3..b16fdb9 100644
--- a/src/nanoarrow/utils.c
+++ b/src/nanoarrow/utils.c
@@ -49,7 +49,13 @@ int ArrowErrorSet(struct ArrowError* error, const char* fmt,
...) {
}
}
-const char* ArrowErrorMessage(struct ArrowError* error) { return
error->message; }
+const char* ArrowErrorMessage(struct ArrowError* error) {
+ if (error == NULL) {
+ return "";
+ } else {
+ return error->message;
+ }
+}
void ArrowLayoutInit(struct ArrowLayout* layout, enum ArrowType storage_type) {
layout->buffer_type[0] = NANOARROW_BUFFER_TYPE_VALIDITY;
diff --git a/src/nanoarrow/utils_test.cc b/src/nanoarrow/utils_test.cc
index 74bb377..57d47eb 100644
--- a/src/nanoarrow/utils_test.cc
+++ b/src/nanoarrow/utils_test.cc
@@ -31,14 +31,23 @@ TEST(BuildIdTest, VersionTest) {
EXPECT_EQ(ArrowNanoarrowVersionInt(), NANOARROW_VERSION_INT);
}
+TEST(ErrorTest, ErrorTestInit) {
+ struct ArrowError error;
+ memset(&error.message, 0xff, sizeof(ArrowError));
+ ArrowErrorInit(&error);
+ EXPECT_STREQ(ArrowErrorMessage(&error), "");
+ ArrowErrorInit(nullptr);
+ EXPECT_STREQ(ArrowErrorMessage(nullptr), "");
+}
+
TEST(ErrorTest, ErrorTestSet) {
- ArrowError error;
+ struct ArrowError error;
EXPECT_EQ(ArrowErrorSet(&error, "there were %d foxes", 4), NANOARROW_OK);
EXPECT_STREQ(ArrowErrorMessage(&error), "there were 4 foxes");
}
TEST(ErrorTest, ErrorTestSetOverrun) {
- ArrowError error;
+ struct ArrowError error;
char big_error[2048];
const char* a_few_chars = "abcdefg";
for (int i = 0; i < 2047; i++) {
@@ -53,6 +62,22 @@ TEST(ErrorTest, ErrorTestSetOverrun) {
EXPECT_EQ(ArrowErrorSet(&error, "%ls", bad_string), EINVAL);
}
+#if defined(NANOARROW_DEBUG)
+#undef NANOARROW_PRINT_AND_DIE
+#define NANOARROW_PRINT_AND_DIE(VALUE, EXPR_STR) \
+ do { \
+ ArrowErrorSet(&error, "%s failed with errno", EXPR_STR); \
+ } while (0)
+
+TEST(ErrorTest, ErrorTestAssertNotOkDebug) {
+ struct ArrowError error;
+ NANOARROW_ASSERT_OK(EINVAL);
+ EXPECT_STREQ(ArrowErrorMessage(&error), "EINVAL failed with errno");
+}
+#else
+TEST(ErrorTest, ErrorTestAssertNotOkRelease) { NANOARROW_ASSERT_OK(EINVAL); }
+#endif
+
static uint8_t* MemoryPoolReallocate(struct ArrowBufferAllocator* allocator,
uint8_t* ptr,
int64_t old_size, int64_t new_size) {
MemoryPool* pool = reinterpret_cast<MemoryPool*>(allocator->private_data);