@@ -643,3 +643,171 @@ TEST(TypeAwareCompressCodecTest, UnsupportedType) {
643643 auto result = TypeAwareCompressCodec::compress (dummy, 8 , dummy, 100 , kSomeUnsupportedType );
644644 ASSERT_FALSE (result.ok ());
645645}
646+
647+ TEST (TypeAwareCompressCodecTest, Int128Supported) {
648+ ASSERT_TRUE (TypeAwareCompressCodec::support (tac::kUInt128 ));
649+ }
650+
651+ TEST (TypeAwareCompressCodecTest, Int128NarrowRoundtrip) {
652+ // 1024 INT128 values where the high 64 bits are 0 (typical DECIMAL fitting
653+ // in int64) and the low 64 bits range narrowly. Both hi and lo sub-streams
654+ // should compress extremely well.
655+ constexpr int64_t kNumValues = 1024 ;
656+ std::vector<uint8_t > data (kNumValues * sizeof (__int128_t ), 0 );
657+ for (int64_t i = 0 ; i < kNumValues ; ++i) {
658+ uint64_t lo = 1000000ULL + static_cast <uint64_t >(i);
659+ std::memcpy (data.data () + i * sizeof (__int128_t ), &lo, sizeof (uint64_t ));
660+ }
661+
662+ const int64_t inputSize = data.size ();
663+ auto maxLen = TypeAwareCompressCodec::maxCompressedLen (inputSize, tac::kUInt128 );
664+ ASSERT_GT (maxLen, 0 );
665+
666+ std::vector<uint8_t > compressed (maxLen);
667+ auto compResult =
668+ TypeAwareCompressCodec::compress (data.data (), inputSize, compressed.data (), compressed.size (), tac::kUInt128 );
669+ ASSERT_TRUE (compResult.ok ()) << compResult.status ().ToString ();
670+ const int64_t compressedSize = *compResult;
671+
672+ ASSERT_LT (compressedSize, inputSize / 2 );
673+
674+ std::vector<uint8_t > decoded (inputSize, 0xff );
675+ auto decResult = TypeAwareCompressCodec::decompress (compressed.data (), compressedSize, decoded.data (), inputSize);
676+ ASSERT_TRUE (decResult.ok ()) << decResult.status ().ToString ();
677+ ASSERT_EQ (*decResult, compressedSize);
678+ ASSERT_EQ (0 , std::memcmp (decoded.data (), data.data (), inputSize));
679+ }
680+
681+ TEST (TypeAwareCompressCodecTest, Int128HighEntropyRoundtrip) {
682+ // High-entropy 128-bit values: both halves are random. Verifies round-trip
683+ // correctness on a workload FFor cannot compress effectively.
684+ constexpr int64_t kNumValues = 1024 ;
685+ std::vector<uint8_t > data (kNumValues * sizeof (__int128_t ));
686+ std::mt19937_64 rng (42 );
687+ for (int64_t i = 0 ; i < kNumValues ; ++i) {
688+ uint64_t lo = rng ();
689+ uint64_t hi = rng ();
690+ std::memcpy (data.data () + i * sizeof (__int128_t ), &lo, sizeof (uint64_t ));
691+ std::memcpy (data.data () + i * sizeof (__int128_t ) + 8 , &hi, sizeof (uint64_t ));
692+ }
693+
694+ const int64_t inputSize = data.size ();
695+ auto maxLen = TypeAwareCompressCodec::maxCompressedLen (inputSize, tac::kUInt128 );
696+ std::vector<uint8_t > compressed (maxLen);
697+ auto compResult =
698+ TypeAwareCompressCodec::compress (data.data (), inputSize, compressed.data (), compressed.size (), tac::kUInt128 );
699+ ASSERT_TRUE (compResult.ok ()) << compResult.status ().ToString ();
700+
701+ std::vector<uint8_t > decoded (inputSize);
702+ auto decResult = TypeAwareCompressCodec::decompress (compressed.data (), *compResult, decoded.data (), inputSize);
703+ ASSERT_TRUE (decResult.ok ()) << decResult.status ().ToString ();
704+ ASSERT_EQ (*decResult, *compResult);
705+ ASSERT_EQ (0 , std::memcmp (decoded.data (), data.data (), inputSize));
706+ }
707+
708+ namespace {
709+
710+ // Helper: build kNumValues 128-bit values whose lo halves form a narrow
711+ // arithmetic sequence and hi halves are zero (typical "DECIMAL fits in int64"
712+ // pattern). Returns the raw byte buffer.
713+ std::vector<uint8_t > makeNarrowInt128 (int64_t kNumValues ) {
714+ std::vector<uint8_t > data (kNumValues * sizeof (__int128_t ), 0 );
715+ for (int64_t i = 0 ; i < kNumValues ; ++i) {
716+ uint64_t lo = 1000000ULL + static_cast <uint64_t >(i);
717+ std::memcpy (data.data () + i * sizeof (__int128_t ), &lo, sizeof (uint64_t ));
718+ }
719+ return data;
720+ }
721+
722+ } // namespace
723+
724+ TEST (TypeAwareCompressCodecTest, Int128TailRoundtrip) {
725+ // Element count that is NOT a multiple of kLanes (=4): 1027 = 256*4 + 3.
726+ // Forces compress128 to emit 1 full block plus a 3-element tail block,
727+ // exercising the kBwTailMarker path in both compress and decompress.
728+ constexpr int64_t kNumValues = 1027 ;
729+ auto data = makeNarrowInt128 (kNumValues );
730+
731+ const int64_t inputSize = data.size ();
732+ auto maxLen = TypeAwareCompressCodec::maxCompressedLen (inputSize, tac::kUInt128 );
733+ std::vector<uint8_t > compressed (maxLen);
734+ auto compResult =
735+ TypeAwareCompressCodec::compress (data.data (), inputSize, compressed.data (), compressed.size (), tac::kUInt128 );
736+ ASSERT_TRUE (compResult.ok ()) << compResult.status ().ToString ();
737+ const int64_t compressedSize = *compResult;
738+
739+ std::vector<uint8_t > decoded (inputSize, 0xff );
740+ auto decResult = TypeAwareCompressCodec::decompress (compressed.data (), compressedSize, decoded.data (), inputSize);
741+ ASSERT_TRUE (decResult.ok ()) << decResult.status ().ToString ();
742+ ASSERT_EQ (0 , std::memcmp (decoded.data (), data.data (), inputSize));
743+ }
744+
745+ TEST (TypeAwareCompressCodecTest, Int128MisalignedRoundtrip) {
746+ // Drive compress128/decompress128 with input AND output pointers offset by
747+ // 1 byte so neither is uint64-aligned. Exercises the <false, false>
748+ // template instantiation, which is otherwise dead code under the natural
749+ // alignment of std::vector<uint8_t>::data().
750+ constexpr int64_t kNumValues = 1024 ;
751+ auto src = makeNarrowInt128 (kNumValues );
752+ const int64_t inputSize = src.size ();
753+
754+ // Stage src into a buffer whose payload starts at a 1-byte-offset address.
755+ std::vector<uint8_t > inBuf (inputSize + 1 , 0 );
756+ std::memcpy (inBuf.data () + 1 , src.data (), inputSize);
757+
758+ auto maxLen = TypeAwareCompressCodec::maxCompressedLen (inputSize, tac::kUInt128 );
759+ std::vector<uint8_t > compBuf (maxLen + 1 , 0 );
760+ // Skip the first byte to misalign the compressed-data start as well.
761+ auto compResult =
762+ TypeAwareCompressCodec::compress (inBuf.data () + 1 , inputSize, compBuf.data () + 1 , maxLen, tac::kUInt128 );
763+ ASSERT_TRUE (compResult.ok ()) << compResult.status ().ToString ();
764+ const int64_t compressedSize = *compResult;
765+
766+ std::vector<uint8_t > outBuf (inputSize + 1 , 0xff );
767+ auto decResult = TypeAwareCompressCodec::decompress (compBuf.data () + 1 , compressedSize, outBuf.data () + 1 , inputSize);
768+ ASSERT_TRUE (decResult.ok ()) << decResult.status ().ToString ();
769+ ASSERT_EQ (0 , std::memcmp (outBuf.data () + 1 , src.data (), inputSize));
770+ }
771+
772+ TEST (TypeAwareCompressCodecTest, Int128TruncatedInputRejected) {
773+ // Compress a normal stream, then hand decompress() a prefix that drops the
774+ // terminator tail header plus the final block's hi sub-header, leaving the
775+ // truncation point inside the final block's lo payload. decompress128Impl
776+ // must detect the short read via decodeBlock's bounds check and return a
777+ // value count below the expected number of 128-bit values, which
778+ // TypeAwareCompressCodec turns into an Invalid status.
779+ constexpr int64_t kNumValues = 1024 ;
780+ auto data = makeNarrowInt128 (kNumValues );
781+ const int64_t inputSize = data.size ();
782+ auto maxLen = TypeAwareCompressCodec::maxCompressedLen (inputSize, tac::kUInt128 );
783+ std::vector<uint8_t > compressed (maxLen);
784+ auto compResult =
785+ TypeAwareCompressCodec::compress (data.data (), inputSize, compressed.data (), compressed.size (), tac::kUInt128 );
786+ ASSERT_TRUE (compResult.ok ()) << compResult.status ().ToString ();
787+ const int64_t compressedSize = *compResult;
788+ // 64 bytes = 16B tail header + 16B hi sub-header + 32B into the final lo
789+ // payload. Smaller drops can land on a clean tail boundary and silently
790+ // succeed.
791+ ASSERT_GT (compressedSize, 64 );
792+
793+ std::vector<uint8_t > decoded (inputSize, 0 );
794+ auto decResult =
795+ TypeAwareCompressCodec::decompress (compressed.data (), compressedSize - 64 , decoded.data (), inputSize);
796+ ASSERT_FALSE (decResult.ok ());
797+ }
798+
799+ TEST (TypeAwareCompressCodecTest, Int128OutputBufferTooSmallRejected) {
800+ // compress128 must reject an output buffer smaller than maxCompressedLen,
801+ // since the codec writes worst-case-sized headers up-front and only knows
802+ // the final length after the analyze step.
803+ constexpr int64_t kNumValues = 64 ;
804+ auto data = makeNarrowInt128 (kNumValues );
805+ const int64_t inputSize = data.size ();
806+ auto maxLen = TypeAwareCompressCodec::maxCompressedLen (inputSize, tac::kUInt128 );
807+ ASSERT_GT (maxLen, 0 );
808+
809+ std::vector<uint8_t > tooSmall (maxLen - 1 );
810+ auto result =
811+ TypeAwareCompressCodec::compress (data.data (), inputSize, tooSmall.data (), tooSmall.size (), tac::kUInt128 );
812+ ASSERT_FALSE (result.ok ());
813+ }
0 commit comments