This is an automated email from the ASF dual-hosted git repository.

zhouyuan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git


The following commit(s) were added to refs/heads/main by this push:
     new 9a2a8be7e8 [VL] extend TypeAwareCompress to INT128 in shuffle (#12299)
9a2a8be7e8 is described below

commit 9a2a8be7e8a7661d94fdaab79747709163ef108e
Author: hu hengrui <[email protected]>
AuthorDate: Tue Jun 23 22:15:16 2026 +0800

    [VL] extend TypeAwareCompress to INT128 in shuffle (#12299)
    
    * [VL]:shuffle: extend TypeAwareCompress to INT128 (DECIMAL HUGEINT)
    
    The TAC codec from #11894 only covered INT64.  DECIMAL(p>18) is stored as
    128-bit HugeInt and previously fell through to LZ4, missing a structural
    compression opportunity: in OLAP workloads (TPC-H prices, taxes, ...)
    DECIMAL values usually fit in INT64, so the high 64 bits are 0 and the
    low 64 bits are narrow -- exactly the pattern FFOR exploits.
    
    Implementation: split each 16B value into lo/hi uint64 sub-streams via
    stride-2 gather into stack-allocated scratch buffers, then run the
    existing 64-bit FFOR encoder on each.  Wire format reuses the 64-bit
    per-block (bw, count, base) header twice -- the stream is self-
    describing, no hi/lo length prefix needed.  hi sub-streams that are all
    equal to base degenerate to just the 16B header (bw=0).  Velox HUGEINT
    is mapped to tac::kUInt128; shuffle writer and frame format unchanged.
    
    Results vs LZ4 on the same int128 input:
      compression ratio  0.116 (TAC)  vs  0.193 (LZ4)  -- 40% smaller
    
    End-to-end on TPC-H SF=6000, the wins concentrate on the queries with
    heavy decimal-keyed shuffles:
      q15: shuffle size -15%, latency -8%
      q17: shuffle size -8%,  latency -3%
      q18: shuffle size -8%,  latency -3%
    Generated-by: Claude claude-opus-4-7
    
    Co-authored-by: Guo Wangyang <[email protected]>
    Co-authored-by: Lipeng Zhu <[email protected]>
    Signed-off-by: huhengrui <[email protected]>
    
    * format: fix clang-format violations in PR #12299
    
    clang-format-15 -Werror flagged four spots introduced by 27c53a85
    (extend TypeAwareCompress to INT128).  No semantic change.
    
      - ffor.hpp:354  writeHeader call: argument-pack break style.
      - ffor.hpp:394  trailing comment missing space after //.
      - ffor.hpp:473  std::memcpy fits on one line under 120-col limit.
      - ffor.hpp:667  dst64 assignment fits on one line.
    
    To be squashed into 27c53a85 before merge.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    
    * fix: bound 64-bit FFOR decompress by output buffer capacity
    
    decompress64(input, inputSize, output) has no way to know the caller's
    output buffer capacity, so a corrupt or truncated stream header can
    drive the decoder to write past the end of `output` before the caller
    can detect a value-count mismatch.
    
    Two specific OOB paths exposed today:
    
      - Tail block: `count` is read straight from the wire (`inPtr[1]`).
        Previously gated only by `inPtr + tailBytes <= inEnd`, so a header
        with count=0xFF copies 255*8 = 2040 bytes into output regardless
        of how much room the caller actually has.
    
      - Regular block: `blockVals = inPtr[1] * kLanes` is gated only by
        `<= kMaxValuesPerBlock`.  A maximum-sized block is happily decoded
        even when the caller's buffer is much smaller, overflowing output.
    
    Either case is a heap/stack OOB write triggered by malformed shuffle
    bytes.  The 128-bit path already guards against this via outValuesMax
    (ffor.hpp L634/L642); this brings the 64-bit path to parity.
    
    Changes:
    
      - decompress64{,Impl}: add `outputSize`, derive
        `outValuesMax = outputSize / sizeof(uint64_t)`, and reject any
        tail `count` or block `blockVals` that would exceed
        `outValuesMax - nDecoded`.
      - FForCodec::decompress: forward the existing `outputSize` arg
        (previously dropped at the call site) into ffor::decompress64.
      - Tests: update the 5 existing decompress64 call sites to pass the
        output buffer capacity.
    
    No behavior change for well-formed streams.  On malformed input, the
    loop stops cleanly at the first oversized header instead of writing
    past `output`; callers still detect the short result via the existing
    nDecoded vs expected-value-count check in TypeAwareCompressCodec.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
    Co-authored-by: Guo Wangyang <[email protected]>
    Co-authored-by: Lipeng Zhu <[email protected]>
    Signed-off-by: huhengrui <[email protected]>
    
    ---------
    
    Signed-off-by: huhengrui <[email protected]>
    Co-authored-by: Guo Wangyang <[email protected]>
    Co-authored-by: Lipeng Zhu <[email protected]>
    Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
---
 cpp/core/tests/FForCodecTest.cc              | 178 ++++++++++++++-
 cpp/core/utils/tac/FForCodec.cc              |  49 +++-
 cpp/core/utils/tac/FForCodec.h               |  23 +-
 cpp/core/utils/tac/TypeAwareCompressCodec.cc |  64 ++++--
 cpp/core/utils/tac/TypeAwareCompressCodec.h  |   4 +-
 cpp/core/utils/tac/ffor.hpp                  | 323 +++++++++++++++++++++------
 cpp/velox/shuffle/VeloxTypeAwareCompress.h   |   2 +
 7 files changed, 557 insertions(+), 86 deletions(-)

diff --git a/cpp/core/tests/FForCodecTest.cc b/cpp/core/tests/FForCodecTest.cc
index a723322211..d4ef8aa4b9 100644
--- a/cpp/core/tests/FForCodecTest.cc
+++ b/cpp/core/tests/FForCodecTest.cc
@@ -85,7 +85,7 @@ void compressRoundtrip(const uint64_t* data, size_t num) {
   size_t written = compress64(data, num, buf.data());
 
   std::vector<uint64_t> decoded(num);
-  size_t nDecoded = decompress64(buf.data(), written, decoded.data());
+  size_t nDecoded = decompress64(buf.data(), written, decoded.data(), 
decoded.size() * sizeof(uint64_t));
 
   ASSERT_EQ(nDecoded, num);
   for (size_t i = 0; i < num; ++i) {
@@ -402,7 +402,7 @@ TEST(FForTest, Compress64MisalignedOutput) {
     size_t written = compress64(data.data(), data.size(), out);
 
     std::vector<uint64_t> decoded(256);
-    size_t n = decompress64(out, written, decoded.data());
+    size_t n = decompress64(out, written, decoded.data(), decoded.size() * 
sizeof(uint64_t));
     ASSERT_EQ(n, size_t(256));
     for (size_t i = 0; i < 256; ++i) {
       ASSERT_EQ(decoded[i], data[i]) << "offset=" << offset << " i=" << i;
@@ -422,7 +422,7 @@ TEST(FForTest, Compress64MisalignedInput) {
     size_t written = compress64(misalignedInput, 256, comp.data());
 
     std::vector<uint64_t> decoded(256);
-    size_t n = decompress64(comp.data(), written, decoded.data());
+    size_t n = decompress64(comp.data(), written, decoded.data(), 
decoded.size() * sizeof(uint64_t));
     ASSERT_EQ(n, size_t(256));
     for (size_t i = 0; i < 256; ++i) {
       ASSERT_EQ(decoded[i], raw[i]) << "offset=" << offset << " i=" << i;
@@ -438,7 +438,7 @@ TEST(FForTest, Decompress64MisalignedOutput) {
   std::vector<uint8_t> outBuf(256 * sizeof(uint64_t) + 16);
   for (size_t offset = 0; offset < 8; ++offset) {
     auto* misalignedOutput = reinterpret_cast<uint64_t*>(outBuf.data() + 
offset);
-    size_t n = decompress64(comp.data(), written, misalignedOutput);
+    size_t n = decompress64(comp.data(), written, misalignedOutput, 256 * 
sizeof(uint64_t));
     ASSERT_EQ(n, size_t(256));
     for (size_t i = 0; i < 256; ++i) {
       uint64_t val;
@@ -463,7 +463,7 @@ TEST(FForTest, Compress64AllMisaligned) {
         size_t written = compress64(inPtr, 256, compBuf.data() + compOff);
 
         auto* outPtr = reinterpret_cast<uint64_t*>(outBuf.data() + outOff);
-        size_t n = decompress64(compBuf.data() + compOff, written, outPtr);
+        size_t n = decompress64(compBuf.data() + compOff, written, outPtr, 256 
* sizeof(uint64_t));
         ASSERT_EQ(n, size_t(256));
         for (size_t i = 0; i < 256; ++i) {
           uint64_t val;
@@ -643,3 +643,171 @@ TEST(TypeAwareCompressCodecTest, UnsupportedType) {
   auto result = TypeAwareCompressCodec::compress(dummy, 8, dummy, 100, 
kSomeUnsupportedType);
   ASSERT_FALSE(result.ok());
 }
+
+TEST(TypeAwareCompressCodecTest, Int128Supported) {
+  ASSERT_TRUE(TypeAwareCompressCodec::support(tac::kUInt128));
+}
+
+TEST(TypeAwareCompressCodecTest, Int128NarrowRoundtrip) {
+  // 1024 INT128 values where the high 64 bits are 0 (typical DECIMAL fitting
+  // in int64) and the low 64 bits range narrowly.  Both hi and lo sub-streams
+  // should compress extremely well.
+  constexpr int64_t kNumValues = 1024;
+  std::vector<uint8_t> data(kNumValues * sizeof(__int128_t), 0);
+  for (int64_t i = 0; i < kNumValues; ++i) {
+    uint64_t lo = 1000000ULL + static_cast<uint64_t>(i);
+    std::memcpy(data.data() + i * sizeof(__int128_t), &lo, sizeof(uint64_t));
+  }
+
+  const int64_t inputSize = data.size();
+  auto maxLen = TypeAwareCompressCodec::maxCompressedLen(inputSize, 
tac::kUInt128);
+  ASSERT_GT(maxLen, 0);
+
+  std::vector<uint8_t> compressed(maxLen);
+  auto compResult =
+      TypeAwareCompressCodec::compress(data.data(), inputSize, 
compressed.data(), compressed.size(), tac::kUInt128);
+  ASSERT_TRUE(compResult.ok()) << compResult.status().ToString();
+  const int64_t compressedSize = *compResult;
+
+  ASSERT_LT(compressedSize, inputSize / 2);
+
+  std::vector<uint8_t> decoded(inputSize, 0xff);
+  auto decResult = TypeAwareCompressCodec::decompress(compressed.data(), 
compressedSize, decoded.data(), inputSize);
+  ASSERT_TRUE(decResult.ok()) << decResult.status().ToString();
+  ASSERT_EQ(*decResult, compressedSize);
+  ASSERT_EQ(0, std::memcmp(decoded.data(), data.data(), inputSize));
+}
+
+TEST(TypeAwareCompressCodecTest, Int128HighEntropyRoundtrip) {
+  // High-entropy 128-bit values: both halves are random.  Verifies round-trip
+  // correctness on a workload FFor cannot compress effectively.
+  constexpr int64_t kNumValues = 1024;
+  std::vector<uint8_t> data(kNumValues * sizeof(__int128_t));
+  std::mt19937_64 rng(42);
+  for (int64_t i = 0; i < kNumValues; ++i) {
+    uint64_t lo = rng();
+    uint64_t hi = rng();
+    std::memcpy(data.data() + i * sizeof(__int128_t), &lo, sizeof(uint64_t));
+    std::memcpy(data.data() + i * sizeof(__int128_t) + 8, &hi, 
sizeof(uint64_t));
+  }
+
+  const int64_t inputSize = data.size();
+  auto maxLen = TypeAwareCompressCodec::maxCompressedLen(inputSize, 
tac::kUInt128);
+  std::vector<uint8_t> compressed(maxLen);
+  auto compResult =
+      TypeAwareCompressCodec::compress(data.data(), inputSize, 
compressed.data(), compressed.size(), tac::kUInt128);
+  ASSERT_TRUE(compResult.ok()) << compResult.status().ToString();
+
+  std::vector<uint8_t> decoded(inputSize);
+  auto decResult = TypeAwareCompressCodec::decompress(compressed.data(), 
*compResult, decoded.data(), inputSize);
+  ASSERT_TRUE(decResult.ok()) << decResult.status().ToString();
+  ASSERT_EQ(*decResult, *compResult);
+  ASSERT_EQ(0, std::memcmp(decoded.data(), data.data(), inputSize));
+}
+
+namespace {
+
+// Helper: build kNumValues 128-bit values whose lo halves form a narrow
+// arithmetic sequence and hi halves are zero (typical "DECIMAL fits in int64"
+// pattern).  Returns the raw byte buffer.
+std::vector<uint8_t> makeNarrowInt128(int64_t kNumValues) {
+  std::vector<uint8_t> data(kNumValues * sizeof(__int128_t), 0);
+  for (int64_t i = 0; i < kNumValues; ++i) {
+    uint64_t lo = 1000000ULL + static_cast<uint64_t>(i);
+    std::memcpy(data.data() + i * sizeof(__int128_t), &lo, sizeof(uint64_t));
+  }
+  return data;
+}
+
+} // namespace
+
+TEST(TypeAwareCompressCodecTest, Int128TailRoundtrip) {
+  // Element count that is NOT a multiple of kLanes (=4): 1027 = 256*4 + 3.
+  // Forces compress128 to emit 1 full block plus a 3-element tail block,
+  // exercising the kBwTailMarker path in both compress and decompress.
+  constexpr int64_t kNumValues = 1027;
+  auto data = makeNarrowInt128(kNumValues);
+
+  const int64_t inputSize = data.size();
+  auto maxLen = TypeAwareCompressCodec::maxCompressedLen(inputSize, 
tac::kUInt128);
+  std::vector<uint8_t> compressed(maxLen);
+  auto compResult =
+      TypeAwareCompressCodec::compress(data.data(), inputSize, 
compressed.data(), compressed.size(), tac::kUInt128);
+  ASSERT_TRUE(compResult.ok()) << compResult.status().ToString();
+  const int64_t compressedSize = *compResult;
+
+  std::vector<uint8_t> decoded(inputSize, 0xff);
+  auto decResult = TypeAwareCompressCodec::decompress(compressed.data(), 
compressedSize, decoded.data(), inputSize);
+  ASSERT_TRUE(decResult.ok()) << decResult.status().ToString();
+  ASSERT_EQ(0, std::memcmp(decoded.data(), data.data(), inputSize));
+}
+
+TEST(TypeAwareCompressCodecTest, Int128MisalignedRoundtrip) {
+  // Drive compress128/decompress128 with input AND output pointers offset by
+  // 1 byte so neither is uint64-aligned.  Exercises the <false, false>
+  // template instantiation, which is otherwise dead code under the natural
+  // alignment of std::vector<uint8_t>::data().
+  constexpr int64_t kNumValues = 1024;
+  auto src = makeNarrowInt128(kNumValues);
+  const int64_t inputSize = src.size();
+
+  // Stage src into a buffer whose payload starts at a 1-byte-offset address.
+  std::vector<uint8_t> inBuf(inputSize + 1, 0);
+  std::memcpy(inBuf.data() + 1, src.data(), inputSize);
+
+  auto maxLen = TypeAwareCompressCodec::maxCompressedLen(inputSize, 
tac::kUInt128);
+  std::vector<uint8_t> compBuf(maxLen + 1, 0);
+  // Skip the first byte to misalign the compressed-data start as well.
+  auto compResult =
+      TypeAwareCompressCodec::compress(inBuf.data() + 1, inputSize, 
compBuf.data() + 1, maxLen, tac::kUInt128);
+  ASSERT_TRUE(compResult.ok()) << compResult.status().ToString();
+  const int64_t compressedSize = *compResult;
+
+  std::vector<uint8_t> outBuf(inputSize + 1, 0xff);
+  auto decResult = TypeAwareCompressCodec::decompress(compBuf.data() + 1, 
compressedSize, outBuf.data() + 1, inputSize);
+  ASSERT_TRUE(decResult.ok()) << decResult.status().ToString();
+  ASSERT_EQ(0, std::memcmp(outBuf.data() + 1, src.data(), inputSize));
+}
+
+TEST(TypeAwareCompressCodecTest, Int128TruncatedInputRejected) {
+  // Compress a normal stream, then hand decompress() a prefix that drops the
+  // terminator tail header plus the final block's hi sub-header, leaving the
+  // truncation point inside the final block's lo payload.  decompress128Impl
+  // must detect the short read via decodeBlock's bounds check and return a
+  // value count below the expected number of 128-bit values, which
+  // TypeAwareCompressCodec turns into an Invalid status.
+  constexpr int64_t kNumValues = 1024;
+  auto data = makeNarrowInt128(kNumValues);
+  const int64_t inputSize = data.size();
+  auto maxLen = TypeAwareCompressCodec::maxCompressedLen(inputSize, 
tac::kUInt128);
+  std::vector<uint8_t> compressed(maxLen);
+  auto compResult =
+      TypeAwareCompressCodec::compress(data.data(), inputSize, 
compressed.data(), compressed.size(), tac::kUInt128);
+  ASSERT_TRUE(compResult.ok()) << compResult.status().ToString();
+  const int64_t compressedSize = *compResult;
+  // 64 bytes = 16B tail header + 16B hi sub-header + 32B into the final lo
+  // payload.  Smaller drops can land on a clean tail boundary and silently
+  // succeed.
+  ASSERT_GT(compressedSize, 64);
+
+  std::vector<uint8_t> decoded(inputSize, 0);
+  auto decResult =
+      TypeAwareCompressCodec::decompress(compressed.data(), compressedSize - 
64, decoded.data(), inputSize);
+  ASSERT_FALSE(decResult.ok());
+}
+
+TEST(TypeAwareCompressCodecTest, Int128OutputBufferTooSmallRejected) {
+  // compress128 must reject an output buffer smaller than maxCompressedLen,
+  // since the codec writes worst-case-sized headers up-front and only knows
+  // the final length after the analyze step.
+  constexpr int64_t kNumValues = 64;
+  auto data = makeNarrowInt128(kNumValues);
+  const int64_t inputSize = data.size();
+  auto maxLen = TypeAwareCompressCodec::maxCompressedLen(inputSize, 
tac::kUInt128);
+  ASSERT_GT(maxLen, 0);
+
+  std::vector<uint8_t> tooSmall(maxLen - 1);
+  auto result =
+      TypeAwareCompressCodec::compress(data.data(), inputSize, 
tooSmall.data(), tooSmall.size(), tac::kUInt128);
+  ASSERT_FALSE(result.ok());
+}
diff --git a/cpp/core/utils/tac/FForCodec.cc b/cpp/core/utils/tac/FForCodec.cc
index ec079662f9..f31eb1a36c 100644
--- a/cpp/core/utils/tac/FForCodec.cc
+++ b/cpp/core/utils/tac/FForCodec.cc
@@ -37,7 +37,8 @@ FForCodec::compress(const uint8_t* input, int64_t inputSize, 
uint8_t* output, in
   size_t numValues = inputSize / sizeof(uint64_t);
   auto maxLen = static_cast<int64_t>(ffor::compress64Bound(numValues));
   if (outputSize < maxLen) {
-    return arrow::Status::Invalid("FForCodec: output buffer too small.");
+    return arrow::Status::Invalid(
+        "FForCodec: output buffer too small (need ", maxLen, " bytes, got ", 
outputSize, ").");
   }
 
   auto written = ffor::compress64(reinterpret_cast<const uint64_t*>(input), 
numValues, output);
@@ -53,7 +54,51 @@ FForCodec::decompress(const uint8_t* input, int64_t 
inputSize, uint8_t* output,
     return arrow::Status::Invalid("FForCodec: output size ", outputSize, " is 
not a multiple of 8.");
   }
 
-  auto nDecoded = ffor::decompress64(input, inputSize, 
reinterpret_cast<uint64_t*>(output));
+  auto nDecoded =
+      ffor::decompress64(input, inputSize, 
reinterpret_cast<uint64_t*>(output), static_cast<size_t>(outputSize));
+  return static_cast<int64_t>(nDecoded);
+}
+
+int64_t FForCodec::maxCompressedLength128(int64_t inputSize) {
+  if (inputSize % sizeof(__int128_t) != 0) {
+    return 0;
+  }
+  size_t numValues = inputSize / sizeof(__int128_t);
+  return static_cast<int64_t>(ffor::compress128Bound(numValues));
+}
+
+arrow::Result<int64_t>
+FForCodec::compress128(const uint8_t* input, int64_t inputSize, uint8_t* 
output, int64_t outputSize) {
+  if (inputSize == 0) {
+    return 0;
+  }
+  if (inputSize % sizeof(__int128_t) != 0) {
+    return arrow::Status::Invalid(
+        "FForCodec: input size ", inputSize, " is not a multiple of ", 
sizeof(__int128_t), ".");
+  }
+
+  size_t numValues = inputSize / sizeof(__int128_t);
+  auto maxLen = static_cast<int64_t>(ffor::compress128Bound(numValues));
+  if (outputSize < maxLen) {
+    return arrow::Status::Invalid(
+        "FForCodec: output buffer too small for 128-bit compression (need ", 
maxLen, " bytes, got ", outputSize, ").");
+  }
+
+  auto written = ffor::compress128(input, numValues, output);
+  return static_cast<int64_t>(written);
+}
+
+arrow::Result<int64_t>
+FForCodec::decompress128(const uint8_t* input, int64_t inputSize, uint8_t* 
output, int64_t outputSize) {
+  if (outputSize == 0) {
+    return 0;
+  }
+  if (outputSize % sizeof(__int128_t) != 0) {
+    return arrow::Status::Invalid(
+        "FForCodec: output size ", outputSize, " is not a multiple of ", 
sizeof(__int128_t), ".");
+  }
+
+  auto nDecoded = ffor::decompress128(input, inputSize, output, 
static_cast<size_t>(outputSize));
   return static_cast<int64_t>(nDecoded);
 }
 
diff --git a/cpp/core/utils/tac/FForCodec.h b/cpp/core/utils/tac/FForCodec.h
index b91a13860a..a176e00f33 100644
--- a/cpp/core/utils/tac/FForCodec.h
+++ b/cpp/core/utils/tac/FForCodec.h
@@ -23,11 +23,12 @@
 
 namespace gluten {
 
-// FFOR (Frame-of-Reference) codec for uint64_t data using 4-lane layout.
-// Used for INT64/UINT64 columns in shuffle.
+// FFOR (Frame-of-Reference) codec for uint64_t / 128-bit data using 4-lane 
layout.
+// Used for INT64/UINT64 and INT128/HUGEINT (DECIMAL) columns in shuffle.
 class FForCodec {
  public:
   // Returns the maximum compressed size in bytes for the given input size.
+  // Input is treated as a stream of uint64 values; size must be a multiple of 
8.
   static int64_t maxCompressedLength(int64_t inputSize);
 
   // Compress uint64_t data.
@@ -40,6 +41,24 @@ class FForCodec {
   // Returns the number of uint64_t values decoded.
   static arrow::Result<int64_t>
   decompress(const uint8_t* input, int64_t inputSize, uint8_t* output, int64_t 
outputSize);
+
+  // Worst-case compressed size for a stream of 128-bit values.
+  // inputSize must be a multiple of sizeof(__int128_t); returns 0 otherwise.
+  static int64_t maxCompressedLength128(int64_t inputSize);
+
+  // Compress a stream of 128-bit values whose in-memory layout consists of
+  // two 64-bit halves: the low 8B at offset 0 and the high 8B at offset 8
+  // (DECIMAL128 in Velox). Internally splits each value into hi/lo uint64
+  // sub-streams per block and runs the 64-bit FFOR encoder on each.
+  // inputSize must be a multiple of sizeof(__int128_t); returns the number
+  // of compressed bytes written to output.
+  static arrow::Result<int64_t>
+  compress128(const uint8_t* input, int64_t inputSize, uint8_t* output, 
int64_t outputSize);
+
+  // Decompress data produced by compress128().  outputSize must be a multiple
+  // of sizeof(__int128_t); returns the number of 128-bit values decoded.
+  static arrow::Result<int64_t>
+  decompress128(const uint8_t* input, int64_t inputSize, uint8_t* output, 
int64_t outputSize);
 };
 
 } // namespace gluten
diff --git a/cpp/core/utils/tac/TypeAwareCompressCodec.cc 
b/cpp/core/utils/tac/TypeAwareCompressCodec.cc
index 2362f999b5..507aa972f6 100644
--- a/cpp/core/utils/tac/TypeAwareCompressCodec.cc
+++ b/cpp/core/utils/tac/TypeAwareCompressCodec.cc
@@ -21,14 +21,18 @@
 namespace gluten {
 
 bool TypeAwareCompressCodec::support(int8_t tacType) {
-  return tacType == tac::kUInt64;
+  return tacType == tac::kUInt64 || tacType == tac::kUInt128;
 }
 
 int64_t TypeAwareCompressCodec::maxCompressedLen(int64_t inputLen, int8_t 
tacType) {
-  if (!support(tacType)) {
-    return 0;
+  switch (tacType) {
+    case tac::kUInt64:
+      return kPayloadHeaderSize + FForCodec::maxCompressedLength(inputLen);
+    case tac::kUInt128:
+      return kPayloadHeaderSize + FForCodec::maxCompressedLength128(inputLen);
+    default:
+      return 0;
   }
-  return kPayloadHeaderSize + FForCodec::maxCompressedLength(inputLen);
 }
 
 arrow::Result<int64_t> TypeAwareCompressCodec::compress(
@@ -50,10 +54,21 @@ arrow::Result<int64_t> TypeAwareCompressCodec::compress(
   auto* out = output;
   *out++ = static_cast<uint8_t>(CodecId::kFFor);
   *out++ = static_cast<uint8_t>(tacType);
+  int64_t availableOutput = outputLen - kPayloadHeaderSize;
 
-  auto availableOutput = outputLen - kPayloadHeaderSize;
-  ARROW_ASSIGN_OR_RAISE(auto compressedLen, FForCodec::compress(input, 
inputLen, out, availableOutput));
-
+  int64_t compressedLen = 0;
+  switch (tacType) {
+    case tac::kUInt64: {
+      ARROW_ASSIGN_OR_RAISE(compressedLen, FForCodec::compress(input, 
inputLen, out, availableOutput));
+      break;
+    }
+    case tac::kUInt128: {
+      ARROW_ASSIGN_OR_RAISE(compressedLen, FForCodec::compress128(input, 
inputLen, out, availableOutput));
+      break;
+    }
+    default:
+      return arrow::Status::Invalid("Unsupported tac type in compress: ", 
static_cast<int>(tacType));
+  }
   return kPayloadHeaderSize + compressedLen;
 }
 
@@ -65,18 +80,41 @@ TypeAwareCompressCodec::decompress(const uint8_t* input, 
int64_t inputLen, uint8
 
   auto* in = input;
   auto codecId = static_cast<CodecId>(*in++);
-  [[maybe_unused]] auto tacType = *in++;
+  auto tacType = static_cast<int8_t>(*in++);
   auto dataLen = inputLen - kPayloadHeaderSize;
 
   switch (codecId) {
-    case CodecId::kFFor: {
-      ARROW_ASSIGN_OR_RAISE(auto nDecoded, FForCodec::decompress(in, dataLen, 
output, outputLen));
-      (void)nDecoded;
-      return inputLen;
-    }
+    case CodecId::kFFor:
+      break;
     default:
       return arrow::Status::Invalid("Unknown type-aware codec ID: ", 
static_cast<int>(codecId));
   }
+
+  int64_t nDecoded = 0;
+  int64_t valueSize = 0;
+  const char* typeName = nullptr;
+  switch (tacType) {
+    case tac::kUInt64: {
+      ARROW_ASSIGN_OR_RAISE(nDecoded, FForCodec::decompress(in, dataLen, 
output, outputLen));
+      valueSize = sizeof(uint64_t);
+      typeName = "uint64";
+      break;
+    }
+    case tac::kUInt128: {
+      ARROW_ASSIGN_OR_RAISE(nDecoded, FForCodec::decompress128(in, dataLen, 
output, outputLen));
+      valueSize = 2 * sizeof(uint64_t);
+      typeName = "uint128";
+      break;
+    }
+    default:
+      return arrow::Status::Invalid("Unknown tac type in decompress: ", 
static_cast<int>(tacType));
+  }
+  const int64_t expected = outputLen / valueSize;
+  if (nDecoded != expected) {
+    return arrow::Status::Invalid(
+        "TAC decompress ", typeName, " value count mismatch: expected ", 
expected, " got ", nDecoded);
+  }
+  return inputLen;
 }
 
 } // namespace gluten
diff --git a/cpp/core/utils/tac/TypeAwareCompressCodec.h 
b/cpp/core/utils/tac/TypeAwareCompressCodec.h
index 6955525d2e..635c36ee78 100644
--- a/cpp/core/utils/tac/TypeAwareCompressCodec.h
+++ b/cpp/core/utils/tac/TypeAwareCompressCodec.h
@@ -30,6 +30,7 @@ namespace tac {
 enum TacDataType : int8_t {
   kUnsupported = -1, // Not compressible by TAC.
   kUInt64 = 0, // 8-byte unsigned integer (also used for int64, double, 
date64).
+  kUInt128 = 1, // 16-byte unsigned integer (used for HUGEINT / DECIMAL128).
 };
 
 } // namespace tac
@@ -38,7 +39,8 @@ enum TacDataType : int8_t {
 /// compression algorithm based on the data type of the buffer.
 ///
 /// Currently supported:
-///   kUInt64 -> FFor (Frame-of-Reference + Bit-Packing) for uint64_t streams.
+///   kUInt64  -> FFOR (Frame-of-Reference + Bit-Packing) for uint64_t streams.
+///   kUInt128 -> FFOR applied to lo/hi uint64 sub-streams of 128-bit values.
 ///
 /// The compressed wire format is self-describing: decompress() does not need
 /// a type hint because codec ID and element width are embedded in the header.
diff --git a/cpp/core/utils/tac/ffor.hpp b/cpp/core/utils/tac/ffor.hpp
index 0d632efff5..d659d3bb35 100644
--- a/cpp/core/utils/tac/ffor.hpp
+++ b/cpp/core/utils/tac/ffor.hpp
@@ -50,6 +50,7 @@
 
 #pragma once
 
+#include <algorithm>
 #include <array>
 #include <cstddef>
 #include <cstdint>
@@ -59,6 +60,13 @@
 namespace gluten {
 namespace ffor {
 
+// Byte order (applies to the 128-bit codec below): this codec round-trips
+// data as native uint64 reads/writes against the lo (offset 0) and hi
+// (offset 8) halves of each 128-bit value (DECIMAL128's in-memory layout in
+// Velox).  Producer and consumer share byte order by virtue of running in
+// the same Spark cluster -- LZ4 and any other shuffle codec carry the same
+// implicit assumption -- so no explicit endian guard is needed here.
+
 static constexpr unsigned kLanes = 4;
 
 // Compile-time mask for a given bit width.
@@ -335,15 +343,55 @@ inline constexpr size_t compress64Bound(size_t num) {
   return (nBlocks + 1) * kHeaderSize + num * sizeof(uint64_t);
 }
 
+// Encode one block: write header + bit-packed payload into `out`.
+// Returns the number of bytes written.  `out` must be 8-byte aligned;
+// callers whose output buffer is unaligned stage through a local scratch.
+// Shared by the 64-bit and 128-bit codecs.
+inline size_t encodeBlock(const uint64_t* src, size_t blockVals, uint64_t* 
out) {
+  uint64_t base;
+  unsigned bw;
+  analyze(src, blockVals, base, bw);
+  writeHeader(
+      reinterpret_cast<uint8_t*>(out), static_cast<uint8_t>(bw), 
static_cast<uint8_t>(blockVals / kLanes), base);
+  const size_t compWords = compressedWords(blockVals, bw);
+  encodeRt(src, out + 2, base, blockVals, bw);
+  return (2 + compWords) * sizeof(uint64_t);
+}
+
+// Decode one block of `blockVals` values from `in` into `dst`.
+// Returns the number of bytes consumed, or 0 on failure (corrupt header or
+// payload that would read past inEnd).  `in` must be 8-byte aligned;
+// callers whose input buffer is unaligned stage through a local scratch.
+// Shared by the 64-bit and 128-bit codecs.
+inline size_t decodeBlock(const uint64_t* in, size_t inBytes, size_t 
blockVals, uint64_t* dst) {
+  if (inBytes < kHeaderSize) {
+    return 0;
+  }
+  uint8_t bw;
+  uint8_t count;
+  uint64_t base;
+  readHeader(reinterpret_cast<const uint8_t*>(in), bw, count, base);
+  if (bw > 64 || bw == kBwTailMarker || static_cast<size_t>(count) * kLanes != 
blockVals) {
+    return 0;
+  }
+  const size_t compWords = compressedWords(blockVals, bw);
+  const size_t totalBytes = (2 + compWords) * sizeof(uint64_t);
+  if (totalBytes > inBytes) {
+    return 0;
+  }
+  decodeRt(in + 2, dst, base, blockVals, bw);
+  return totalBytes;
+}
+
 // Template-based compress/decompress with alignment dispatch.
 // InAligned:  true if input  (const uint64_t*) is 8-byte aligned.
 // OutAligned: true if output (uint8_t*) is 8-byte aligned.
-// When aligned, encode_rt/decode_rt work on pointers directly.
-// When not aligned, a per-block aligned temp buffer is used.
+// encodeBlock requires aligned uint64_t* output; when the caller's output
+// buffer is unaligned, we stage through tmpOut and memcpy back.
 template <bool InAligned, bool OutAligned>
 inline size_t compress64Impl(const uint64_t* input, size_t num, uint8_t* 
output) {
   alignas(64) uint64_t tmpIn[kMaxValuesPerBlock];
-  alignas(64) uint64_t tmpOut[kMaxValuesPerBlock];
+  alignas(64) uint64_t tmpOut[kMaxValuesPerBlock + 2]; // header(2 words) + 
payload
 
   uint8_t* outPtr = output;
   size_t remaining = num;
@@ -355,35 +403,21 @@ inline size_t compress64Impl(const uint64_t* input, 
size_t num, uint8_t* output)
       blockVals = kMaxValuesPerBlock;
     }
 
-    // Analyze — read input via memcpy if unaligned.
-    const uint64_t* analyzeSrc;
+    const uint64_t* src;
     if constexpr (InAligned) {
-      analyzeSrc = inPtr;
+      src = inPtr;
     } else {
       std::memcpy(tmpIn, inPtr, blockVals * sizeof(uint64_t));
-      analyzeSrc = tmpIn;
+      src = tmpIn;
     }
 
-    uint64_t base;
-    unsigned bw;
-    analyze(analyzeSrc, blockVals, base, bw);
-
-    writeHeader(outPtr, static_cast<uint8_t>(bw), 
static_cast<uint8_t>(blockVals / kLanes), base);
-    outPtr += kHeaderSize;
-
-    size_t compN = compressedWords(blockVals, bw);
-    size_t compBytes = compN * sizeof(uint64_t);
-
-    // Encode: pick aligned src/dst.
-    const uint64_t* encIn = InAligned ? inPtr : tmpIn;
-    uint64_t* encOut = OutAligned ? reinterpret_cast<uint64_t*>(outPtr) : 
tmpOut;
-
-    encodeRt(encIn, encOut, base, blockVals, bw);
-
-    if constexpr (!OutAligned) {
-      std::memcpy(outPtr, tmpOut, compBytes);
+    if constexpr (OutAligned) {
+      outPtr += encodeBlock(src, blockVals, 
reinterpret_cast<uint64_t*>(outPtr));
+    } else {
+      const size_t produced = encodeBlock(src, blockVals, tmpOut);
+      std::memcpy(outPtr, tmpOut, produced);
+      outPtr += produced;
     }
-    outPtr += compBytes;
 
     inPtr += blockVals;
     remaining -= blockVals;
@@ -418,62 +452,53 @@ inline size_t compress64(const uint64_t* input, size_t 
num, uint8_t* output) {
 }
 
 // Template-based decompress with alignment dispatch.
+// decodeBlock requires aligned uint64_t* input; when the caller's input
+// buffer is unaligned, we stage through tmpIn and memcpy in.
 template <bool InAligned, bool OutAligned>
-inline size_t decompress64Impl(const uint8_t* input, size_t inputSize, 
uint64_t* output) {
-  alignas(64) uint64_t tmpIn[kMaxValuesPerBlock];
+inline size_t decompress64Impl(const uint8_t* input, size_t inputSize, 
uint64_t* output, size_t outputSize) {
+  alignas(64) uint64_t tmpIn[kMaxValuesPerBlock + 2];
   alignas(64) uint64_t tmpOut[kMaxValuesPerBlock];
 
   const uint8_t* inPtr = input;
   const uint8_t* inEnd = input + inputSize;
+  const size_t outValuesMax = outputSize / sizeof(uint64_t);
   size_t nDecoded = 0;
 
   while (inPtr + kHeaderSize <= inEnd) {
-    uint8_t bw;
-    uint8_t count;
-    uint64_t base;
-    readHeader(inPtr, bw, count, base);
-    inPtr += kHeaderSize;
-
-    if (bw == kBwTailMarker) {
-      if (count > 0) {
-        // memcpy handles any alignment, no special case needed.
-        std::memcpy(reinterpret_cast<uint8_t*>(output) + nDecoded * 
sizeof(uint64_t), inPtr, count * sizeof(uint64_t));
+    if (inPtr[0] == kBwTailMarker) {
+      const uint8_t count = inPtr[1];
+      inPtr += kHeaderSize;
+      const size_t tailBytes = static_cast<size_t>(count) * sizeof(uint64_t);
+      if (count > 0 && inPtr + tailBytes <= inEnd && count <= outValuesMax - 
nDecoded) {
+        std::memcpy(reinterpret_cast<uint8_t*>(output) + nDecoded * 
sizeof(uint64_t), inPtr, tailBytes);
         nDecoded += count;
       }
       break;
     }
-
-    size_t blockVals = static_cast<size_t>(count) * kLanes;
-    size_t compBytes = compressedWords(blockVals, bw) * sizeof(uint64_t);
-
-    if (inPtr + compBytes > inEnd) {
+    const size_t blockVals = static_cast<size_t>(inPtr[1]) * kLanes;
+    if (blockVals == 0 || blockVals > kMaxValuesPerBlock || blockVals > 
outValuesMax - nDecoded) {
       break;
     }
+    const size_t remaining = static_cast<size_t>(inEnd - inPtr);
+    uint64_t* decDst = OutAligned ? output + nDecoded : tmpOut;
 
-    // Decode: pick aligned src/dst.
-    const uint64_t* decIn;
+    size_t consumed;
     if constexpr (InAligned) {
-      decIn = reinterpret_cast<const uint64_t*>(inPtr);
+      consumed = decodeBlock(reinterpret_cast<const uint64_t*>(inPtr), 
remaining, blockVals, decDst);
     } else {
-      std::memcpy(tmpIn, inPtr, compBytes);
-      decIn = tmpIn;
+      const size_t n = std::min(remaining, sizeof(tmpIn));
+      std::memcpy(tmpIn, inPtr, n);
+      consumed = decodeBlock(tmpIn, n, blockVals, decDst);
     }
-
-    uint64_t* decOut;
-    if constexpr (OutAligned) {
-      decOut = output + nDecoded;
-    } else {
-      decOut = tmpOut;
+    if (consumed == 0) {
+      break;
     }
-
-    decodeRt(decIn, decOut, base, blockVals, bw);
+    inPtr += consumed;
 
     if constexpr (!OutAligned) {
       std::memcpy(
           reinterpret_cast<uint8_t*>(output) + nDecoded * sizeof(uint64_t), 
tmpOut, blockVals * sizeof(uint64_t));
     }
-
-    inPtr += compBytes;
     nDecoded += blockVals;
   }
 
@@ -481,19 +506,191 @@ inline size_t decompress64Impl(const uint8_t* input, 
size_t inputSize, uint64_t*
 }
 
 // Runtime dispatch.
-inline size_t decompress64(const uint8_t* input, size_t inputSize, uint64_t* 
output) {
+inline size_t decompress64(const uint8_t* input, size_t inputSize, uint64_t* 
output, size_t outputSize) {
   bool inOk = (reinterpret_cast<uintptr_t>(input) % alignof(uint64_t) == 0);
   bool outOk = (reinterpret_cast<uintptr_t>(output) % alignof(uint64_t) == 0);
   if (inOk && outOk) {
-    return decompress64Impl<true, true>(input, inputSize, output);
+    return decompress64Impl<true, true>(input, inputSize, output, outputSize);
+  }
+  if (inOk && !outOk) {
+    return decompress64Impl<true, false>(input, inputSize, output, outputSize);
+  }
+  if (!inOk && outOk) {
+    return decompress64Impl<false, true>(input, inputSize, output, outputSize);
+  }
+  return decompress64Impl<false, false>(input, inputSize, output, outputSize);
+}
+
+// 
=============================================================================
+// 128-bit codec.
+//
+// Each 128-bit value occupies a 16B slot (lo at offset 0, hi at offset 8 --
+// the DECIMAL128 / __int128_t layout used by Velox).  Per block, the lo and
+// hi halves are gathered into two stack scratches and each is fed through
+// the 64-bit FFOR encoder.  Reads/writes go through native uint64, so the
+// codec is byte-order agnostic as long as producer and consumer agree.
+//
+// Wire format per block:  [hdr][lo payload][hdr][hi payload]
+// followed by one tail block (kBwTailMarker) carrying the remaining 16B
+// values raw.
+// 
=============================================================================
+
+inline constexpr size_t compress128Bound(size_t numValues) {
+  // Two 64-bit streams (lo + hi), worst case each = 
compress64Bound(numValues).
+  return 2 * compress64Bound(numValues);
+}
+
+// 128-bit compress.  See encodeBlock for InAligned/OutAligned semantics.
+template <bool InAligned, bool OutAligned>
+inline size_t compress128Impl(const uint8_t* input, size_t numValues, uint8_t* 
output) {
+  alignas(64) uint64_t loBuffer[kMaxValuesPerBlock];
+  alignas(64) uint64_t hiBuffer[kMaxValuesPerBlock];
+  alignas(64) uint64_t tmpIn[kMaxValuesPerBlock * 2];
+  alignas(64) uint64_t tmpOut[kMaxValuesPerBlock + 2]; // header(2 words) + 
payload
+
+  uint8_t* outPtr = output;
+  size_t remaining = numValues;
+  const uint8_t* inPtr = input;
+
+  while (remaining >= kLanes) {
+    size_t blockVals = remaining - (remaining % kLanes);
+    if (blockVals > kMaxValuesPerBlock) {
+      blockVals = kMaxValuesPerBlock;
+    }
+
+    const uint64_t* in64;
+    if constexpr (InAligned) {
+      in64 = reinterpret_cast<const uint64_t*>(inPtr);
+    } else {
+      std::memcpy(tmpIn, inPtr, blockVals * sizeof(__int128_t));
+      in64 = tmpIn;
+    }
+    for (size_t j = 0; j < blockVals; ++j) {
+      loBuffer[j] = in64[j * 2];
+      hiBuffer[j] = in64[j * 2 + 1];
+    }
+
+    if constexpr (OutAligned) {
+      outPtr += encodeBlock(loBuffer, blockVals, 
reinterpret_cast<uint64_t*>(outPtr));
+      outPtr += encodeBlock(hiBuffer, blockVals, 
reinterpret_cast<uint64_t*>(outPtr));
+    } else {
+      size_t n = encodeBlock(loBuffer, blockVals, tmpOut);
+      std::memcpy(outPtr, tmpOut, n);
+      outPtr += n;
+      n = encodeBlock(hiBuffer, blockVals, tmpOut);
+      std::memcpy(outPtr, tmpOut, n);
+      outPtr += n;
+    }
+
+    inPtr += blockVals * sizeof(__int128_t);
+    remaining -= blockVals;
+  }
+
+  // Tail: one header + remaining values copied raw.
+  writeHeader(outPtr, kBwTailMarker, static_cast<uint8_t>(remaining), 0);
+  outPtr += kHeaderSize;
+  if (remaining > 0) {
+    std::memcpy(outPtr, inPtr, remaining * sizeof(__int128_t));
+    outPtr += remaining * sizeof(__int128_t);
+  }
+  return static_cast<size_t>(outPtr - output);
+}
+
+// Runtime dispatch — check alignment once, pick the right template.
+inline size_t compress128(const uint8_t* input, size_t numValues, uint8_t* 
output) {
+  const bool inOk = (reinterpret_cast<uintptr_t>(input) % alignof(uint64_t) == 
0);
+  const bool outOk = (reinterpret_cast<uintptr_t>(output) % alignof(uint64_t) 
== 0);
+  if (inOk && outOk) {
+    return compress128Impl<true, true>(input, numValues, output);
+  }
+  if (inOk && !outOk) {
+    return compress128Impl<true, false>(input, numValues, output);
+  }
+  if (!inOk && outOk) {
+    return compress128Impl<false, true>(input, numValues, output);
+  }
+  return compress128Impl<false, false>(input, numValues, output);
+}
+
+// 128-bit decompress.  decodeBlock requires aligned uint64_t* input; when
+// the caller's input buffer is unaligned, we stage through tmpIn.
+template <bool InAligned, bool OutAligned>
+inline size_t decompress128Impl(const uint8_t* input, size_t inputSize, 
uint8_t* output, size_t outputSize) {
+  alignas(64) uint64_t loBuffer[kMaxValuesPerBlock];
+  alignas(64) uint64_t hiBuffer[kMaxValuesPerBlock];
+  alignas(64) uint64_t tmpIn[kMaxValuesPerBlock + 2];
+  alignas(64) uint64_t tmpOut[kMaxValuesPerBlock * 2];
+
+  const uint8_t* inPtr = input;
+  const uint8_t* inEnd = input + inputSize;
+  const size_t outValuesMax = outputSize / sizeof(__int128_t);
+  size_t nDecoded = 0;
+
+  while (inPtr + kHeaderSize <= inEnd) {
+    if (inPtr[0] == kBwTailMarker) {
+      const uint8_t count = inPtr[1];
+      inPtr += kHeaderSize;
+      const size_t tailBytes = static_cast<size_t>(count) * sizeof(__int128_t);
+      if (inPtr + tailBytes > inEnd || count > outValuesMax - nDecoded) {
+        break;
+      }
+      std::memcpy(output + nDecoded * sizeof(__int128_t), inPtr, tailBytes);
+      nDecoded += count;
+      break;
+    }
+    const size_t blockVals = static_cast<size_t>(inPtr[1]) * kLanes;
+    if (blockVals == 0 || blockVals > kMaxValuesPerBlock || blockVals > 
outValuesMax - nDecoded) {
+      break;
+    }
+
+    auto decodeOne = [&](uint64_t* dst) -> bool {
+      const size_t remaining = static_cast<size_t>(inEnd - inPtr);
+      size_t consumed;
+      if constexpr (InAligned) {
+        consumed = decodeBlock(reinterpret_cast<const uint64_t*>(inPtr), 
remaining, blockVals, dst);
+      } else {
+        const size_t n = std::min(remaining, sizeof(tmpIn));
+        std::memcpy(tmpIn, inPtr, n);
+        consumed = decodeBlock(tmpIn, n, blockVals, dst);
+      }
+      if (consumed == 0) {
+        return false;
+      }
+      inPtr += consumed;
+      return true;
+    };
+    if (!decodeOne(loBuffer) || !decodeOne(hiBuffer)) {
+      break;
+    }
+
+    uint64_t* dst64 = OutAligned ? reinterpret_cast<uint64_t*>(output + 
nDecoded * sizeof(__int128_t)) : tmpOut;
+    for (size_t j = 0; j < blockVals; ++j) {
+      dst64[j * 2] = loBuffer[j];
+      dst64[j * 2 + 1] = hiBuffer[j];
+    }
+    if constexpr (!OutAligned) {
+      std::memcpy(output + nDecoded * sizeof(__int128_t), tmpOut, blockVals * 
sizeof(__int128_t));
+    }
+    nDecoded += blockVals;
+  }
+
+  return nDecoded;
+}
+
+// Runtime dispatch — check alignment once, pick the right template.
+inline size_t decompress128(const uint8_t* input, size_t inputSize, uint8_t* 
output, size_t outputSize) {
+  const bool inOk = (reinterpret_cast<uintptr_t>(input) % alignof(uint64_t) == 
0);
+  const bool outOk = (reinterpret_cast<uintptr_t>(output) % alignof(uint64_t) 
== 0);
+  if (inOk && outOk) {
+    return decompress128Impl<true, true>(input, inputSize, output, outputSize);
   }
   if (inOk && !outOk) {
-    return decompress64Impl<true, false>(input, inputSize, output);
+    return decompress128Impl<true, false>(input, inputSize, output, 
outputSize);
   }
   if (!inOk && outOk) {
-    return decompress64Impl<false, true>(input, inputSize, output);
+    return decompress128Impl<false, true>(input, inputSize, output, 
outputSize);
   }
-  return decompress64Impl<false, false>(input, inputSize, output);
+  return decompress128Impl<false, false>(input, inputSize, output, outputSize);
 }
 
 } // namespace ffor
diff --git a/cpp/velox/shuffle/VeloxTypeAwareCompress.h 
b/cpp/velox/shuffle/VeloxTypeAwareCompress.h
index fbbabc5c06..370ee92151 100644
--- a/cpp/velox/shuffle/VeloxTypeAwareCompress.h
+++ b/cpp/velox/shuffle/VeloxTypeAwareCompress.h
@@ -28,6 +28,8 @@ inline int8_t veloxTypeToTacType(facebook::velox::TypeKind 
kind) {
   switch (kind) {
     case facebook::velox::TypeKind::BIGINT:
       return tac::kUInt64;
+    case facebook::velox::TypeKind::HUGEINT:
+      return tac::kUInt128;
     default:
       return tac::kUnsupported;
   }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to