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 f341741d feat: Add LZ4 decompression support to IPC reader (#819)
f341741d is described below
commit f341741d26d810c1481e541600a64bb2e354b4eb
Author: Dewey Dunnington <[email protected]>
AuthorDate: Mon Oct 27 09:41:39 2025 -0500
feat: Add LZ4 decompression support to IPC reader (#819)
Closes #727.
---------
Co-authored-by: Copilot <[email protected]>
---
.github/workflows/build-and-test-ipc.yaml | 4 +-
CMakeLists.txt | 34 +++++++++++++-
ci/scripts/coverage.sh | 1 +
src/nanoarrow/ipc/codecs.c | 62 +++++++++++++++++++++++++
src/nanoarrow/ipc/codecs_test.cc | 75 ++++++++++++++++++++++++++++---
src/nanoarrow/ipc/decoder_test.cc | 43 +++++++++++++++---
src/nanoarrow/ipc/files_test.cc | 4 +-
src/nanoarrow/nanoarrow_ipc.h | 7 +++
8 files changed, 214 insertions(+), 16 deletions(-)
diff --git a/.github/workflows/build-and-test-ipc.yaml
b/.github/workflows/build-and-test-ipc.yaml
index d9a36561..e10dc3bf 100644
--- a/.github/workflows/build-and-test-ipc.yaml
+++ b/.github/workflows/build-and-test-ipc.yaml
@@ -43,7 +43,7 @@ jobs:
fail-fast: false
matrix:
config:
- - {label: default-build, cmake_args: "-DNANOARROW_BUILD_APPS=ON
-DNANOARROW_IPC_WITH_ZSTD=ON"}
+ - {label: default-build, cmake_args: "-DNANOARROW_BUILD_APPS=ON
-DNANOARROW_IPC_WITH_ZSTD=ON -DNANOARROW_IPC_WITH_LZ4=ON"}
- {label: default-noatomics, cmake_args:
"-DCMAKE_C_FLAGS='-DNANOARROW_IPC_USE_STDATOMIC=0'"}
- {label: shared-test-linkage, cmake_args:
"-DNANOARROW_TEST_LINKAGE_SHARED=ON"}
- {label: namespaced-build, cmake_args:
"-DNANOARROW_NAMESPACE=SomeUserNamespace"}
@@ -73,7 +73,7 @@ jobs:
with:
path: arrow
# Bump the number at the end of this line to force a new Arrow C++
build
- key: arrow-${{ runner.os }}-${{ runner.arch }}-4
+ key: arrow-${{ runner.os }}-${{ runner.arch }}-5
- name: Build Arrow C++
if: steps.cache-arrow-build.outputs.cache-hit != 'true'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e9691778..0d70bf1e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -40,6 +40,7 @@ option(NANOARROW_FLATCC_INCLUDE_DIR "Include directory for
flatcc includes" OFF)
option(NANOARROW_FLATCC_LIB_DIR "Library directory that contains
libflatccrt.a" OFF)
option(NANOARROW_IPC_WITH_ZSTD "Build nanoarrow with ZSTD compression support
built in"
OFF)
+option(NANOARROW_IPC_WITH_LZ4 "Build nanoarrow with LZ4 compression support
built in" OFF)
option(NANOARROW_DEVICE "Build device extension" OFF)
option(NANOARROW_TESTING "Build testing extension" OFF)
@@ -247,6 +248,32 @@ if(NANOARROW_IPC)
endif()
endif()
+ if(NANOARROW_IPC_WITH_LZ4)
+ if(NOT LZ4_LIBRARY_DIRS AND NOT LZ4_INCLUDE_DIRS)
+ find_package(PkgConfig REQUIRED)
+ pkg_check_modules(LZ4 REQUIRED liblz4)
+ endif()
+
+ if(NOT LZ4_LIBRARIES)
+ set(LZ4_LIBRARIES lz4)
+ endif()
+
+ message(STATUS "LZ4 library directories: ${LZ4_LIBRARY_DIRS}")
+ message(STATUS "LZ4 include directories: ${LZ4_INCLUDE_DIRS}")
+ message(STATUS "LZ4 libraries: ${LZ4_LIBRARIES}")
+
+ add_library(lz4::lz4 INTERFACE IMPORTED)
+ target_include_directories(lz4::lz4 INTERFACE ${LZ4_INCLUDE_DIRS})
+ target_link_libraries(lz4::lz4 INTERFACE ${LZ4_LIBRARIES})
+ if(LZ4_LIBRARY_DIRS)
+ set_target_properties(lz4::lz4 PROPERTIES INTERFACE_LINK_DIRECTORIES
+ "${LZ4_LIBRARY_DIRS}")
+ endif()
+
+ set(NANOARROW_IPC_EXTRA_FLAGS ${NANOARROW_IPC_EXTRA_FLAGS}
"-DNANOARROW_IPC_WITH_LZ4")
+ set(NANOARROW_IPC_EXTRA_LIBS ${NANOARROW_IPC_EXTRA_LIBS} lz4::lz4)
+ endif()
+
if(NOT NANOARROW_BUNDLE)
set(NANOARROW_IPC_BUILD_SOURCES
src/nanoarrow/ipc/codecs.c
@@ -296,8 +323,11 @@ if(NANOARROW_IPC AND (NANOARROW_BUILD_INTEGRATION_TESTS OR
NANOARROW_BUILD_TESTS
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src>
$<INSTALL_INTERFACE:include>)
target_link_libraries(nanoarrow_ipc_integration
- PRIVATE nanoarrow_testing_static nanoarrow_ipc_static
flatccrt
- nanoarrow_coverage_config)
+ PRIVATE nanoarrow_testing_static
+ nanoarrow_ipc_static
+ flatccrt
+ nanoarrow_coverage_config
+ ${NANOARROW_IPC_EXTRA_LIBS})
endif()
if(NANOARROW_DEVICE)
diff --git a/ci/scripts/coverage.sh b/ci/scripts/coverage.sh
index c22a7856..908d3dca 100755
--- a/ci/scripts/coverage.sh
+++ b/ci/scripts/coverage.sh
@@ -74,6 +74,7 @@ function main() {
cmake "${TARGET_NANOARROW_DIR}" \
-DNANOARROW_DEVICE=ON -DNANOARROW_IPC=ON
-DNANOARROW_IPC_WITH_ZSTD=ON \
+ -DNANOARROW_IPC_WITH_LZ4=ON \
-DNANOARROW_BUILD_TESTS=ON -DNANOARROW_BUILD_TESTS_WITH_ARROW=ON \
-DNANOARROW_CODE_COVERAGE=ON
cmake --build .
diff --git a/src/nanoarrow/ipc/codecs.c b/src/nanoarrow/ipc/codecs.c
index 552674ce..ab297989 100644
--- a/src/nanoarrow/ipc/codecs.c
+++ b/src/nanoarrow/ipc/codecs.c
@@ -54,6 +54,65 @@ ArrowIpcDecompressFunction
ArrowIpcGetZstdDecompressionFunction(void) {
#endif
}
+#if defined(NANOARROW_IPC_WITH_LZ4)
+#include <lz4.h>
+#include <lz4frame.h>
+
+static ArrowErrorCode ArrowIpcDecompressLZ4(struct ArrowBufferView src,
uint8_t* dst,
+ int64_t dst_size, struct
ArrowError* error) {
+ LZ4F_errorCode_t ret;
+ LZ4F_decompressionContext_t ctx = NULL;
+
+ ret = LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION);
+ if (LZ4F_isError(ret)) {
+ ArrowErrorSet(error, "LZ4 init failed");
+ return EIO;
+ }
+
+ size_t dst_capacity = dst_size;
+ size_t src_size = src.size_bytes;
+ ret = LZ4F_decompress(ctx, dst, &dst_capacity, src.data.data, &src_size,
+ NULL /* options */);
+ if (LZ4F_isError(ret)) {
+ NANOARROW_UNUSED(LZ4F_freeDecompressionContext(ctx));
+ ArrowErrorSet(error,
+ "LZ4F_decompress([buffer with %" PRId64
+ " bytes] -> [buffer with %" PRId64 " bytes]) failed",
+ src.size_bytes, dst_size);
+ return EIO;
+ }
+
+ if ((int64_t)dst_capacity != dst_size) {
+ NANOARROW_UNUSED(LZ4F_freeDecompressionContext(ctx));
+ ArrowErrorSet(error,
+ "Expected decompressed size of %" PRId64 " bytes but got %"
PRId64
+ " bytes",
+ dst_size, (int64_t)dst_capacity);
+ return EIO;
+ }
+
+ if (ret != 0) {
+ NANOARROW_UNUSED(LZ4F_freeDecompressionContext(ctx));
+ ArrowErrorSet(error,
+ "Expected complete LZ4 frame but found frame with %" PRId64
+ " bytes remaining",
+ (int64_t)ret);
+ return EIO;
+ }
+
+ NANOARROW_UNUSED(LZ4F_freeDecompressionContext(ctx));
+ return NANOARROW_OK;
+}
+#endif
+
+ArrowIpcDecompressFunction ArrowIpcGetLZ4DecompressionFunction(void) {
+#if defined(NANOARROW_IPC_WITH_LZ4)
+ return &ArrowIpcDecompressLZ4;
+#else
+ return NULL;
+#endif
+}
+
struct ArrowIpcSerialDecompressorPrivate {
ArrowIpcDecompressFunction decompress_functions[3];
};
@@ -115,6 +174,9 @@ ArrowErrorCode ArrowIpcSerialDecompressor(struct
ArrowIpcDecompressor* decompres
memset(decompressor->private_data, 0, sizeof(struct
ArrowIpcSerialDecompressorPrivate));
ArrowIpcSerialDecompressorSetFunction(decompressor,
NANOARROW_IPC_COMPRESSION_TYPE_ZSTD,
ArrowIpcGetZstdDecompressionFunction());
+ ArrowIpcSerialDecompressorSetFunction(decompressor,
+
NANOARROW_IPC_COMPRESSION_TYPE_LZ4_FRAME,
+ ArrowIpcGetLZ4DecompressionFunction());
return NANOARROW_OK;
}
diff --git a/src/nanoarrow/ipc/codecs_test.cc b/src/nanoarrow/ipc/codecs_test.cc
index 3c19f502..76278174 100644
--- a/src/nanoarrow/ipc/codecs_test.cc
+++ b/src/nanoarrow/ipc/codecs_test.cc
@@ -26,8 +26,8 @@
const uint8_t kZstdCompressed012[] = {0x28, 0xb5, 0x2f, 0xfd, 0x20, 0x0c, 0x61,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
0x00};
-const uint8_t kZstdUncompressed012[] = {0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
- 0x00, 0x00, 0x02, 0x00, 0x00, 0x00};
+const uint8_t kUncompressed012[] = {0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x00};
TEST(NanoarrowIpcTest, NanoarrowIpcZstdBuildMatchesRuntime) {
#if defined(NANOARROW_IPC_WITH_ZSTD)
@@ -51,13 +51,13 @@ TEST(NanoarrowIpcTest, ZstdDecodeValidInput) {
uint8_t out[16];
std::memset(out, 0, sizeof(out));
ASSERT_EQ(decompress({{&kZstdCompressed012}, sizeof(kZstdCompressed012)},
out,
- sizeof(kZstdUncompressed012), &error),
+ sizeof(kUncompressed012), &error),
NANOARROW_OK)
<< error.message;
- EXPECT_TRUE(std::memcmp(out, kZstdUncompressed012,
sizeof(kZstdUncompressed012)) == 0);
+ EXPECT_TRUE(std::memcmp(out, kUncompressed012, sizeof(kUncompressed012)) ==
0);
ASSERT_EQ(decompress({{kZstdCompressed012}, sizeof(kZstdCompressed012)}, out,
- sizeof(kZstdUncompressed012) + 1, &error),
+ sizeof(kUncompressed012) + 1, &error),
EIO);
EXPECT_STREQ(error.message, "Expected decompressed size of 13 bytes but got
12 bytes");
}
@@ -76,6 +76,71 @@ TEST(NanoarrowIpcTest, ZstdDecodeInvalidInput) {
"with 0 bytes]) failed with error"));
}
+// LZ4 compressed little endian int32s [0, 1, 2]
+const uint8_t kLZ4Compressed012[] = {
+ 0x04, 0x22, 0x4d, 0x18, 0x60, 0x40, 0x82, 0x0c, 0x00, 0x00, 0x80, 0x00,
0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00};
+
+TEST(NanoarrowIpcTest, NanoarrowIpcLZ4BuildMatchesRuntime) {
+#if defined(NANOARROW_IPC_WITH_LZ4)
+ ASSERT_NE(ArrowIpcGetLZ4DecompressionFunction(), nullptr);
+#else
+ ASSERT_EQ(ArrowIpcGetLZ4DecompressionFunction(), nullptr);
+#endif
+}
+
+TEST(NanoarrowIpcTest, LZ4DecodeValidInput) {
+ auto decompress = ArrowIpcGetLZ4DecompressionFunction();
+ if (!decompress) {
+ GTEST_SKIP() << "nanoarrow_ipc not built with NANOARROW_IPC_WITH_LZ4";
+ }
+
+ struct ArrowError error {};
+
+ // Check a decompress of a valid compressed buffer
+ uint8_t out[16];
+ std::memset(out, 0, sizeof(out));
+ ASSERT_EQ(decompress({{&kLZ4Compressed012}, sizeof(kLZ4Compressed012)}, out,
+ sizeof(kUncompressed012), &error),
+ NANOARROW_OK)
+ << error.message;
+ EXPECT_TRUE(std::memcmp(out, kUncompressed012, sizeof(kUncompressed012)) ==
0);
+
+ ASSERT_EQ(decompress({{kLZ4Compressed012}, sizeof(kLZ4Compressed012)}, out,
+ sizeof(kUncompressed012) + 1, &error),
+ EIO);
+ EXPECT_STREQ(error.message, "Expected decompressed size of 13 bytes but got
12 bytes");
+}
+
+TEST(NanoarrowIpcTest, LZ4DecodeInvalidInput) {
+ auto decompress = ArrowIpcGetLZ4DecompressionFunction();
+ if (!decompress) {
+ GTEST_SKIP() << "nanoarrow_ipc not built with NANOARROW_IPC_WITH_LZ4";
+ }
+
+ struct ArrowError error {};
+ uint8_t out[16];
+ std::memset(out, 0, sizeof(out));
+
+ // LZ4_decompress() needs almost correct data to trigger this failure branch
+ uint8_t src[16];
+ memcpy(src, kLZ4Compressed012, sizeof(src));
+ src[5] = 0xff;
+ ASSERT_EQ(decompress({{&src}, sizeof(src)}, out, sizeof(kUncompressed012),
&error),
+ EIO);
+ EXPECT_THAT(
+ error.message,
+ ::testing::StartsWith(
+ "LZ4F_decompress([buffer with 16 bytes] -> [buffer with 12 bytes])
failed"));
+
+ // Nonsensical data triggers a different failure branch
+ const char* bad_data = "abcde";
+ EXPECT_EQ(decompress({{bad_data}, 5}, nullptr, 0, &error), EIO);
+ EXPECT_THAT(error.message,
+ ::testing::StartsWith(
+ "Expected complete LZ4 frame but found frame with 6 bytes
remaining"));
+}
+
TEST(NanoarrowIpcTest, SerialDecompressor) {
struct ArrowError error {};
nanoarrow::ipc::UniqueDecompressor decompressor;
diff --git a/src/nanoarrow/ipc/decoder_test.cc
b/src/nanoarrow/ipc/decoder_test.cc
index 058d9fb1..81993bce 100644
--- a/src/nanoarrow/ipc/decoder_test.cc
+++ b/src/nanoarrow/ipc/decoder_test.cc
@@ -108,7 +108,7 @@ alignas(8) static uint8_t kSimpleRecordBatch[] = {
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-alignas(8) static uint8_t kSimpleRecordBatchCompressed[] = {
+alignas(8) static uint8_t kSimpleRecordBatchCompressedZstd[] = {
0xff, 0xff, 0xff, 0xff, 0xa0, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x00, 0x00,
0x00, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x06, 0x00, 0x05, 0x00, 0x08, 0x00,
0x0c, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0x00, 0x1c, 0x00, 0x00, 0x00,
0x20, 0x00,
@@ -125,6 +125,23 @@ alignas(8) static uint8_t kSimpleRecordBatchCompressed[] =
{
0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00,
0x00, 0x00, 0x00, 0x00};
+alignas(8) static uint8_t kSimpleRecordBatchCompressedLZ4[] = {
+ 0xff, 0xff, 0xff, 0xff, 0x98, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x00, 0x00,
+ 0x00, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x06, 0x00, 0x05, 0x00, 0x08, 0x00,
0x0c, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0x00, 0x1c, 0x00, 0x00, 0x00,
0x28, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00,
0x1c, 0x00,
+ 0x10, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x48, 0x00,
+ 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00,
0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00,
0x04, 0x00,
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
+ 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
+ 0x04, 0x22, 0x4d, 0x18, 0x60, 0x40, 0x82, 0x0c, 0x00, 0x00, 0x80, 0x00,
0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00};
+
alignas(8) static uint8_t kSimpleRecordBatchUncompressible[] = {
0xff, 0xff, 0xff, 0xff, 0xa0, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x00, 0x00,
0x00, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x06, 0x00, 0x05, 0x00, 0x08, 0x00,
0x0c, 0x00,
@@ -399,15 +416,29 @@ TEST(NanoarrowIpcTest,
NanoarrowIpcDecodeSimpleRecordBatch) {
TestDecodeInt32Batch(kSimpleRecordBatch, sizeof(kSimpleRecordBatch), {1,
2, 3}));
}
-TEST(NanoarrowIpcTest, NanoarrowIpcDecodeCompressedRecordBatch) {
+TEST(NanoarrowIpcTest, NanoarrowIpcDecodeCompressedRecordBatchZstd) {
if (ArrowIpcGetZstdDecompressionFunction() == nullptr) {
EXPECT_FATAL_FAILURE(
- TestDecodeInt32Batch(kSimpleRecordBatchCompressed,
- sizeof(kSimpleRecordBatchCompressed), {0, 1, 2}),
+ TestDecodeInt32Batch(kSimpleRecordBatchCompressedZstd,
+ sizeof(kSimpleRecordBatchCompressedZstd), {0, 1,
2}),
"Compression type with value 2 not supported by this build of
nanoarrow");
} else {
- ASSERT_NO_FATAL_FAILURE(TestDecodeInt32Batch(
- kSimpleRecordBatchCompressed, sizeof(kSimpleRecordBatchCompressed),
{0, 1, 2}));
+
ASSERT_NO_FATAL_FAILURE(TestDecodeInt32Batch(kSimpleRecordBatchCompressedZstd,
+
sizeof(kSimpleRecordBatchCompressedZstd),
+ {0, 1, 2}));
+ }
+}
+
+TEST(NanoarrowIpcTest, NanoarrowIpcDecodeCompressedRecordBatchLZ4) {
+ if (ArrowIpcGetLZ4DecompressionFunction() == nullptr) {
+ EXPECT_FATAL_FAILURE(
+ TestDecodeInt32Batch(kSimpleRecordBatchCompressedLZ4,
+ sizeof(kSimpleRecordBatchCompressedLZ4), {0, 1,
2}),
+ "Compression type with value 1 not supported by this build of
nanoarrow");
+ } else {
+
ASSERT_NO_FATAL_FAILURE(TestDecodeInt32Batch(kSimpleRecordBatchCompressedLZ4,
+
sizeof(kSimpleRecordBatchCompressedLZ4),
+ {0, 1, 2}));
}
}
diff --git a/src/nanoarrow/ipc/files_test.cc b/src/nanoarrow/ipc/files_test.cc
index 55b988bb..ee2f384c 100644
--- a/src/nanoarrow/ipc/files_test.cc
+++ b/src/nanoarrow/ipc/files_test.cc
@@ -529,13 +529,15 @@ TEST_P(TestFileFixture, NanoarrowIpcTestFileIPCCheckJSON)
{
// At least one Windows MSVC version does not allow the #if defined()
// to be within a macro invocation, so we define these two cases
// with some repetition.
-#if defined(NANOARROW_IPC_WITH_ZSTD)
+#if defined(NANOARROW_IPC_WITH_ZSTD) && defined(NANOARROW_IPC_WITH_LZ4)
INSTANTIATE_TEST_SUITE_P(
NanoarrowIpcTest, TestFileFixture,
::testing::Values(
// Testing of other files
TestFile::OK("2.0.0-compression/generated_uncompressible_zstd.stream"),
TestFile::OK("2.0.0-compression/generated_zstd.stream"),
+ TestFile::OK("2.0.0-compression/generated_uncompressible_lz4.stream"),
+ TestFile::OK("2.0.0-compression/generated_lz4.stream"),
TestFile::OK("0.17.1/generated_union.stream"),
TestFile::OK("0.14.1/generated_datetime.stream"),
TestFile::OK("0.14.1/generated_decimal.stream"),
diff --git a/src/nanoarrow/nanoarrow_ipc.h b/src/nanoarrow/nanoarrow_ipc.h
index 5871d9bf..b9251a6b 100644
--- a/src/nanoarrow/nanoarrow_ipc.h
+++ b/src/nanoarrow/nanoarrow_ipc.h
@@ -31,6 +31,8 @@
NANOARROW_SYMBOL(NANOARROW_NAMESPACE, ArrowIpcSharedBufferReset)
#define ArrowIpcGetZstdDecompressionFunction \
NANOARROW_SYMBOL(NANOARROW_NAMESPACE, ArrowIpcGetZstdDecompressionFunction)
+#define ArrowIpcGetLZ4DecompressionFunction \
+ NANOARROW_SYMBOL(NANOARROW_NAMESPACE, ArrowIpcGetLZ4DecompressionFunction)
#define ArrowIpcSerialDecompressor \
NANOARROW_SYMBOL(NANOARROW_NAMESPACE, ArrowIpcSerialDecompressor)
#define ArrowIpcSerialDecompressorSetFunction \
@@ -253,6 +255,11 @@ typedef ArrowErrorCode
(*ArrowIpcDecompressFunction)(struct ArrowBufferView src,
/// The result will be NULL if nanoarrow was not built with
NANOARROW_IPC_WITH_ZSTD.
NANOARROW_DLL ArrowIpcDecompressFunction
ArrowIpcGetZstdDecompressionFunction(void);
+/// \brief Get the decompression function for LZ4
+///
+/// The result will be NULL if nanoarrow was not built with
NANOARROW_IPC_WITH_LZ4.
+NANOARROW_DLL ArrowIpcDecompressFunction
ArrowIpcGetLZ4DecompressionFunction(void);
+
/// \brief An ArrowIpcDecompressor implementation that performs decompression
in serial
NANOARROW_DLL ArrowErrorCode
ArrowIpcSerialDecompressor(struct ArrowIpcDecompressor* decompressor);