@@ -720,4 +720,125 @@ INSTANTIATE_TEST_SUITE_P(
720720 .source = Literal::Null (iceberg::string ()),
721721 .expected = Literal::Null (iceberg::string ())}));
722722
723+ TEST (TransformPreservesOrderTest, PreservesOrder) {
724+ struct Case {
725+ std::string transform_str;
726+ bool expected;
727+ };
728+
729+ const std::vector<Case> cases = {
730+ {.transform_str = " identity" , .expected = true },
731+ {.transform_str = " year" , .expected = true },
732+ {.transform_str = " month" , .expected = true },
733+ {.transform_str = " day" , .expected = true },
734+ {.transform_str = " hour" , .expected = true },
735+ {.transform_str = " void" , .expected = false },
736+ {.transform_str = " bucket[16]" , .expected = false },
737+ {.transform_str = " truncate[32]" , .expected = true },
738+ };
739+
740+ for (const auto & c : cases) {
741+ auto transform = TransformFromString (c.transform_str );
742+ ASSERT_TRUE (transform.has_value ()) << " Failed to parse: " << c.transform_str ;
743+
744+ EXPECT_EQ (transform.value ()->PreservesOrder (), c.expected )
745+ << " Unexpected result for transform: " << c.transform_str ;
746+ }
747+ }
748+
749+ TEST (TransformSatisfiesOrderOfTest, SatisfiesOrderOf) {
750+ struct Case {
751+ std::string transform_str;
752+ std::string other_transform_str;
753+ bool expected;
754+ };
755+
756+ const std::vector<Case> cases = {
757+ // Identity satisfies all order-preserving transforms
758+ {.transform_str = " identity" , .other_transform_str = " identity" , .expected = true },
759+ {.transform_str = " identity" , .other_transform_str = " year" , .expected = true },
760+ {.transform_str = " identity" , .other_transform_str = " month" , .expected = true },
761+ {.transform_str = " identity" , .other_transform_str = " day" , .expected = true },
762+ {.transform_str = " identity" , .other_transform_str = " hour" , .expected = true },
763+ {.transform_str = " identity" ,
764+ .other_transform_str = " truncate[32]" ,
765+ .expected = true },
766+ {.transform_str = " identity" ,
767+ .other_transform_str = " bucket[16]" ,
768+ .expected = false },
769+
770+ // Truncate satisfies Truncate with smaller width
771+ {.transform_str = " truncate[32]" ,
772+ .other_transform_str = " truncate[16]" ,
773+ .expected = true },
774+ {.transform_str = " truncate[16]" ,
775+ .other_transform_str = " truncate[16]" ,
776+ .expected = true },
777+ {.transform_str = " truncate[16]" ,
778+ .other_transform_str = " truncate[32]" ,
779+ .expected = false },
780+ {.transform_str = " truncate[16]" ,
781+ .other_transform_str = " bucket[32]" ,
782+ .expected = false },
783+
784+ // Hour satisfies hour, day, month, and year
785+ {.transform_str = " hour" , .other_transform_str = " hour" , .expected = true },
786+ {.transform_str = " hour" , .other_transform_str = " day" , .expected = true },
787+ {.transform_str = " hour" , .other_transform_str = " month" , .expected = true },
788+ {.transform_str = " hour" , .other_transform_str = " year" , .expected = true },
789+ {.transform_str = " hour" , .other_transform_str = " identity" , .expected = false },
790+ {.transform_str = " hour" , .other_transform_str = " bucket[16]" , .expected = false },
791+
792+ // Day satisfies day, month, and year
793+ {.transform_str = " day" , .other_transform_str = " day" , .expected = true },
794+ {.transform_str = " day" , .other_transform_str = " month" , .expected = true },
795+ {.transform_str = " day" , .other_transform_str = " year" , .expected = true },
796+ {.transform_str = " day" , .other_transform_str = " hour" , .expected = false },
797+ {.transform_str = " day" , .other_transform_str = " identity" , .expected = false },
798+
799+ // Month satisfies month and year
800+ {.transform_str = " month" , .other_transform_str = " month" , .expected = true },
801+ {.transform_str = " month" , .other_transform_str = " year" , .expected = true },
802+ {.transform_str = " month" , .other_transform_str = " day" , .expected = false },
803+ {.transform_str = " month" , .other_transform_str = " hour" , .expected = false },
804+
805+ // Year satisfies only year
806+ {.transform_str = " year" , .other_transform_str = " year" , .expected = true },
807+ {.transform_str = " year" , .other_transform_str = " month" , .expected = false },
808+ {.transform_str = " year" , .other_transform_str = " day" , .expected = false },
809+ {.transform_str = " year" , .other_transform_str = " hour" , .expected = false },
810+
811+ // Void satisfies no order-preserving transforms
812+ {.transform_str = " void" , .other_transform_str = " identity" , .expected = false },
813+ {.transform_str = " void" , .other_transform_str = " year" , .expected = false },
814+ {.transform_str = " void" , .other_transform_str = " month" , .expected = false },
815+ {.transform_str = " void" , .other_transform_str = " day" , .expected = false },
816+ {.transform_str = " void" , .other_transform_str = " hour" , .expected = false },
817+
818+ // Bucket satisfies only itself
819+ {.transform_str = " bucket[16]" ,
820+ .other_transform_str = " bucket[16]" ,
821+ .expected = true },
822+ {.transform_str = " bucket[16]" ,
823+ .other_transform_str = " bucket[32]" ,
824+ .expected = false },
825+ {.transform_str = " bucket[16]" ,
826+ .other_transform_str = " identity" ,
827+ .expected = false },
828+ };
829+
830+ for (const auto & c : cases) {
831+ auto transform = TransformFromString (c.transform_str );
832+ auto other_transform = TransformFromString (c.other_transform_str );
833+
834+ ASSERT_TRUE (transform.has_value ()) << " Failed to parse: " << c.transform_str ;
835+ ASSERT_TRUE (other_transform.has_value ())
836+ << " Failed to parse: " << c.other_transform_str ;
837+
838+ EXPECT_EQ (transform.value ()->SatisfiesOrderOf (*other_transform.value ()), c.expected )
839+ << " Unexpected result for transform: " << c.transform_str
840+ << " and other transform: " << c.other_transform_str ;
841+ }
842+ }
843+
723844} // namespace iceberg
0 commit comments