paleolimbot commented on code in PR #935:
URL: https://github.com/apache/arrow-nanoarrow/pull/935#discussion_r3960838362


##########
src/nanoarrow/ipc/encoder.c:
##########
@@ -49,6 +49,16 @@ struct ArrowIpcEncoderPrivate {
   // Metadata to attach to the next encoded Message (in nanoarrow's packed
   // representation), or an empty buffer if the next Message has no metadata.
   struct ArrowBuffer message_metadata;
+  // Compression applied to the body buffers of subsequently encoded 
RecordBatches
+  enum ArrowIpcCompressionType codec;
+  // Compressor used when codec != NONE (release is NULL until one is needed)
+  struct ArrowIpcCompressor compressor;
+  // Whether compressor was provided by ArrowIpcEncoderSetCompressor()
+  int custom_compressor;
+  // Compression level passed to the compressor when codec != NONE
+  int compression_level;
+  // The flatbuffer equivalent of codec (only meaningful when codec != NONE)
+  ns(CompressionType_enum_t) flatbuf_codec;

Review Comment:
   Can we reduce this to only `codec` and `compressor` or even just 
`compressor` by inlining the codec and/or level into the `ArrowIpcCompressor` 
struct?



##########
src/nanoarrow/ipc/codecs.c:
##########
@@ -113,6 +183,99 @@ ArrowIpcDecompressFunction 
ArrowIpcGetLZ4DecompressionFunction(void) {
 #endif
 }
 
+ArrowIpcCompressFunction ArrowIpcGetLZ4CompressionFunction(void) {
+#if defined(NANOARROW_IPC_WITH_LZ4)
+  return &ArrowIpcCompressLZ4;
+#else
+  return NULL;
+#endif
+}
+
+ArrowErrorCode ArrowIpcGetCompressionLevelRange(
+    enum ArrowIpcCompressionType compression_type, int* min_level_out,
+    int* max_level_out) {
+  NANOARROW_DCHECK(min_level_out != NULL && max_level_out != NULL);
+  NANOARROW_UNUSED(min_level_out);
+  NANOARROW_UNUSED(max_level_out);
+
+  switch (compression_type) {
+    case NANOARROW_IPC_COMPRESSION_TYPE_ZSTD:
+#if defined(NANOARROW_IPC_WITH_ZSTD)
+#if ZSTD_VERSION_NUMBER >= 10400
+      *min_level_out = ZSTD_minCLevel();
+#else
+      // Negative (fast) levels can't be queried before zstd 1.4.0
+      *min_level_out = NANOARROW_IPC_COMPRESSION_LEVEL_DEFAULT;
+#endif
+      *max_level_out = ZSTD_maxCLevel();
+      return NANOARROW_OK;
+#else
+      return ENOTSUP;
+#endif
+    case NANOARROW_IPC_COMPRESSION_TYPE_LZ4_FRAME:
+#if defined(NANOARROW_IPC_WITH_LZ4)
+      // A negative level selects an acceleration of 1 - level, which lz4 caps 
at 65537
+      // (LZ4_ACCELERATION_MAX, which is not part of its public headers)
+      *min_level_out = 1 - 65537;
+      *max_level_out = LZ4F_compressionLevel_max();
+      return NANOARROW_OK;
+#else
+      return ENOTSUP;
+#endif
+    default:
+      return EINVAL;
+  }
+}
+
+// The serial decompressor and compressor keep one function per codec, indexed 
by
+// enum ArrowIpcCompressionType (NONE is never a codec)
+static int ArrowIpcCompressionTypeIsCodec(enum ArrowIpcCompressionType 
compression_type) {
+  switch (compression_type) {
+    case NANOARROW_IPC_COMPRESSION_TYPE_ZSTD:
+    case NANOARROW_IPC_COMPRESSION_TYPE_LZ4_FRAME:
+      return 1;
+    default:
+      return 0;
+  }
+}
+
+const char* ArrowIpcCompressionTypeToString(
+    enum ArrowIpcCompressionType compression_type) {
+  switch (compression_type) {
+    case NANOARROW_IPC_COMPRESSION_TYPE_NONE:
+      return "none";
+    case NANOARROW_IPC_COMPRESSION_TYPE_LZ4_FRAME:
+      return "lz4";
+    case NANOARROW_IPC_COMPRESSION_TYPE_ZSTD:
+      return "zstd";
+    default:
+      return NULL;

Review Comment:
   It's better to have this return `""` or `"<unknown>"` for the inevitable 
user or contributor who uses this in a format string without checking for a 
NULL. I had to fix ArrowTypeToString recently because of gcc warnings.



##########
src/nanoarrow/nanoarrow_ipc.h:
##########
@@ -412,6 +432,102 @@ ArrowIpcSerialDecompressorSetFunction(struct 
ArrowIpcDecompressor* decompressor,
                                       enum ArrowIpcCompressionType 
compression_type,
                                       ArrowIpcDecompressFunction 
decompress_function);
 
+/// \brief Compression level that selects the codec's default level
+#define NANOARROW_IPC_COMPRESSION_LEVEL_DEFAULT 0
+
+/// \brief A user-extensible compressor
+///
+/// The ArrowIpcCompressor is the underlying object that enables buffer 
compression
+/// in the ArrowIpcEncoder. An implementation of a compressor may support more 
than one
+/// ArrowIpcCompressionType.
+struct ArrowIpcCompressor {
+  /// \brief Compress a buffer
+  ///
+  /// Compresses src using compression_type at compression_level and appends 
the
+  /// compressed bytes to dst. Any content already in dst must be preserved 
(i.e.,
+  /// implementations may only append to dst). See ArrowIpcCompressFunction 
for the
+  /// interpretation of compression_level.
+  ArrowErrorCode (*compress)(struct ArrowIpcCompressor* compressor,
+                             enum ArrowIpcCompressionType compression_type,
+                             int compression_level, struct ArrowBufferView src,
+                             struct ArrowBuffer* dst, struct ArrowError* 
error);
+
+  /// \brief Release the compressor and any resources it may be holding
+  ///
+  /// Release callback implementations must set the release member to NULL.
+  /// Callers must check that the release callback is not NULL before calling
+  /// compress() or release().
+  void (*release)(struct ArrowIpcCompressor* compressor);
+
+  /// \brief Implementation-specific opaque data
+  void* private_data;
+};
+
+/// \brief A self-contained compression function
+///
+/// Compresses src at compression_level and appends the compressed bytes to 
dst. Because
+/// the compressed size is not known in advance, implementations are 
responsible for
+/// reserving sufficient space in dst (e.g., using the compression library's 
bound
+/// function) and must only append to dst.
+///
+/// The interpretation of compression_level is codec-specific:
+/// NANOARROW_IPC_COMPRESSION_LEVEL_DEFAULT selects the codec's default level 
and other
+/// values follow the underlying library's conventions (see
+/// ArrowIpcGetCompressionLevelRange()). ArrowIpcEncoderSetCompression() 
rejects levels
+/// outside that range; the built-in functions clamp them if called directly.
+typedef ArrowErrorCode (*ArrowIpcCompressFunction)(struct ArrowBufferView src,
+                                                   int compression_level,
+                                                   struct ArrowBuffer* dst,
+                                                   struct ArrowError* error);
+
+/// \brief Get the compression function for ZSTD
+///
+/// The result will be NULL if nanoarrow was not built with 
NANOARROW_IPC_WITH_ZSTD.
+NANOARROW_DLL ArrowIpcCompressFunction 
ArrowIpcGetZstdCompressionFunction(void);
+
+/// \brief Get the compression function for LZ4
+///
+/// The result will be NULL if nanoarrow was not built with 
NANOARROW_IPC_WITH_LZ4.
+NANOARROW_DLL ArrowIpcCompressFunction ArrowIpcGetLZ4CompressionFunction(void);
+
+/// \brief An ArrowIpcCompressor implementation that performs compression in 
serial
+NANOARROW_DLL ArrowErrorCode
+ArrowIpcSerialCompressor(struct ArrowIpcCompressor* compressor);

Review Comment:
   I think this can take a `compression_type` and `level` parameter if we 
inline those parameters into the ArrowIpcSerialCompressor private data.



##########
src/nanoarrow/nanoarrow_ipc.h:
##########
@@ -412,6 +432,102 @@ ArrowIpcSerialDecompressorSetFunction(struct 
ArrowIpcDecompressor* decompressor,
                                       enum ArrowIpcCompressionType 
compression_type,
                                       ArrowIpcDecompressFunction 
decompress_function);
 
+/// \brief Compression level that selects the codec's default level
+#define NANOARROW_IPC_COMPRESSION_LEVEL_DEFAULT 0
+
+/// \brief A user-extensible compressor
+///
+/// The ArrowIpcCompressor is the underlying object that enables buffer 
compression
+/// in the ArrowIpcEncoder. An implementation of a compressor may support more 
than one
+/// ArrowIpcCompressionType.
+struct ArrowIpcCompressor {
+  /// \brief Compress a buffer
+  ///
+  /// Compresses src using compression_type at compression_level and appends 
the
+  /// compressed bytes to dst. Any content already in dst must be preserved 
(i.e.,
+  /// implementations may only append to dst). See ArrowIpcCompressFunction 
for the
+  /// interpretation of compression_level.
+  ArrowErrorCode (*compress)(struct ArrowIpcCompressor* compressor,
+                             enum ArrowIpcCompressionType compression_type,
+                             int compression_level, struct ArrowBufferView src,
+                             struct ArrowBuffer* dst, struct ArrowError* 
error);

Review Comment:
   I think it would be better for force any parameters (like level and 
compression type) to be captured in the private data, perhaps with a member for 
compression_type since the encoder will need to declare it (callback seems like 
overkill for that).
   
   If there is any way to potentially parallelize compression, it would be 
great to have this struct/its use support that even though the default 
implementation won't. You might need two passes (`compress_start()`, 
`compress_finish()`, passing `i` to identify the buffer). I don't want to spend 
too much time on the best possible parallel encoding, I just want to avoid 
changing the ABI for a trivial two pass setup. I don't mind the exact contract 
as long as there's at least one plausible setup where this works. Also fine if 
there is no easy plausible setup (we can try later pre 1.0).



##########
src/nanoarrow/ipc/encoder.c:
##########
@@ -69,6 +79,11 @@ ArrowErrorCode ArrowIpcEncoderInit(struct ArrowIpcEncoder* 
encoder) {
   ArrowBufferInit(&private->nodes);
   ArrowIpcDictionaryEncodingsInit(&private->dictionary_encodings);
   ArrowBufferInit(&private->message_metadata);
+  private->codec = NANOARROW_IPC_COMPRESSION_TYPE_NONE;
+  private->compressor.release = NULL;
+  private->custom_compressor = 0;
+  private->compression_level = NANOARROW_IPC_COMPRESSION_LEVEL_DEFAULT;
+  private->flatbuf_codec = ns(CompressionType_LZ4_FRAME);

Review Comment:
   Is there another value we can use for `flatbuf_codec` (or can we omit the 
field entirely and recalculate it when needed) that better communicates 
defaultness (rather than a specific value)?



##########
src/nanoarrow/ipc/encoder.c:
##########
@@ -744,6 +886,15 @@ static ArrowErrorCode ArrowIpcEncoderEncodeRecordBatch(
   FLATCC_RETURN_UNLESS_0(Message_header_RecordBatch_start(builder), error);
   FLATCC_RETURN_UNLESS_0(RecordBatch_length_add(builder, array_view->length), 
error);
 
+  if (private->codec != NANOARROW_IPC_COMPRESSION_TYPE_NONE) {
+    FLATCC_RETURN_UNLESS_0(RecordBatch_compression_start(builder), error);
+    FLATCC_RETURN_UNLESS_0(BodyCompression_codec_add(builder, 
private->flatbuf_codec),
+                           error);
+    FLATCC_RETURN_UNLESS_0(
+        BodyCompression_method_add(builder, ns(BodyCompressionMethod_BUFFER)), 
error);
+    FLATCC_RETURN_UNLESS_0(RecordBatch_compression_end(builder), error);
+  }
+

Review Comment:
   In addition to these changes, I believe the use of compression should also 
be declared when writing the Schema message.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to