@@ -720,4 +720,111 @@ 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+ // Hour satisfies hour, day, month, and year
771+ {.transform_str = " hour" , .other_transform_str = " hour" , .expected = true },
772+ {.transform_str = " hour" , .other_transform_str = " day" , .expected = true },
773+ {.transform_str = " hour" , .other_transform_str = " month" , .expected = true },
774+ {.transform_str = " hour" , .other_transform_str = " year" , .expected = true },
775+ {.transform_str = " hour" , .other_transform_str = " identity" , .expected = false },
776+ {.transform_str = " hour" , .other_transform_str = " bucket[16]" , .expected = false },
777+
778+ // Day satisfies day, month, and year
779+ {.transform_str = " day" , .other_transform_str = " day" , .expected = true },
780+ {.transform_str = " day" , .other_transform_str = " month" , .expected = true },
781+ {.transform_str = " day" , .other_transform_str = " year" , .expected = true },
782+ {.transform_str = " day" , .other_transform_str = " hour" , .expected = false },
783+ {.transform_str = " day" , .other_transform_str = " identity" , .expected = false },
784+
785+ // Month satisfies month and year
786+ {.transform_str = " month" , .other_transform_str = " month" , .expected = true },
787+ {.transform_str = " month" , .other_transform_str = " year" , .expected = true },
788+ {.transform_str = " month" , .other_transform_str = " day" , .expected = false },
789+ {.transform_str = " month" , .other_transform_str = " hour" , .expected = false },
790+
791+ // Year satisfies only year
792+ {.transform_str = " year" , .other_transform_str = " year" , .expected = true },
793+ {.transform_str = " year" , .other_transform_str = " month" , .expected = false },
794+ {.transform_str = " year" , .other_transform_str = " day" , .expected = false },
795+ {.transform_str = " year" , .other_transform_str = " hour" , .expected = false },
796+
797+ // Void satisfies no order-preserving transforms
798+ {.transform_str = " void" , .other_transform_str = " identity" , .expected = false },
799+ {.transform_str = " void" , .other_transform_str = " year" , .expected = false },
800+ {.transform_str = " void" , .other_transform_str = " month" , .expected = false },
801+ {.transform_str = " void" , .other_transform_str = " day" , .expected = false },
802+ {.transform_str = " void" , .other_transform_str = " hour" , .expected = false },
803+
804+ // Bucket satisfies only itself
805+ {.transform_str = " bucket[16]" ,
806+ .other_transform_str = " bucket[16]" ,
807+ .expected = true },
808+ {.transform_str = " bucket[16]" ,
809+ .other_transform_str = " bucket[32]" ,
810+ .expected = false },
811+ {.transform_str = " bucket[16]" ,
812+ .other_transform_str = " identity" ,
813+ .expected = false },
814+ };
815+
816+ for (const auto & c : cases) {
817+ auto transform = TransformFromString (c.transform_str );
818+ auto other_transform = TransformFromString (c.other_transform_str );
819+
820+ ASSERT_TRUE (transform.has_value ()) << " Failed to parse: " << c.transform_str ;
821+ ASSERT_TRUE (other_transform.has_value ())
822+ << " Failed to parse: " << c.other_transform_str ;
823+
824+ EXPECT_EQ (transform.value ()->SatisfiesOrderOf (*other_transform.value ()), c.expected )
825+ << " Unexpected result for transform: " << c.transform_str
826+ << " and other transform: " << c.other_transform_str ;
827+ }
828+ }
829+
723830} // namespace iceberg
0 commit comments