1
+ package com .redis .om .spring .search .stream .predicates ;
2
+
3
+ import static org .junit .jupiter .api .Assertions .*;
4
+
5
+ import java .lang .reflect .Field ;
6
+
7
+ import org .junit .jupiter .api .Test ;
8
+
9
+ import com .redis .om .spring .metamodel .SearchFieldAccessor ;
10
+ import com .redis .om .spring .metamodel .indexed .NumericField ;
11
+ import com .redis .om .spring .metamodel .indexed .TagField ;
12
+
13
+ /**
14
+ * Unit test for issue #342: Verifying that predicates with different field types
15
+ * can be combined using the new andAny() and orAny() methods.
16
+ */
17
+ class MixedTypePredicatesTest {
18
+
19
+ static class TestEntity {
20
+ private String nameSpace ;
21
+ private Long relateId ;
22
+ private String status ;
23
+
24
+ public String getNameSpace () { return nameSpace ; }
25
+ public void setNameSpace (String nameSpace ) { this .nameSpace = nameSpace ; }
26
+
27
+ public Long getRelateId () { return relateId ; }
28
+ public void setRelateId (Long relateId ) { this .relateId = relateId ; }
29
+
30
+ public String getStatus () { return status ; }
31
+ public void setStatus (String status ) { this .status = status ; }
32
+ }
33
+
34
+ @ Test
35
+ void testIssue342_CanCombineDifferentTypePredicatesWithAndAny () throws NoSuchFieldException {
36
+ // Create field accessors
37
+ Field nameSpaceField = TestEntity .class .getDeclaredField ("nameSpace" );
38
+ Field relateIdField = TestEntity .class .getDeclaredField ("relateId" );
39
+
40
+ SearchFieldAccessor nameSpaceAccessor = new SearchFieldAccessor ("@nameSpace" , "$.nameSpace" , nameSpaceField );
41
+ SearchFieldAccessor relateIdAccessor = new SearchFieldAccessor ("@relateId" , "$.relateId" , relateIdField );
42
+
43
+ // Create predicates with different types
44
+ TagField <TestEntity , String > nameSpacePredicate = new TagField <>(nameSpaceAccessor , true );
45
+ NumericField <TestEntity , Long > relateIdPredicate = new NumericField <>(relateIdAccessor , true );
46
+
47
+ // Test that we can combine String and Long predicates with andAny()
48
+ SearchFieldPredicate <TestEntity , String > stringPred = nameSpacePredicate .eq ("PERSONAL" );
49
+ SearchFieldPredicate <TestEntity , Long > longPred = relateIdPredicate .eq (100L );
50
+
51
+ // This should compile without type errors
52
+ SearchFieldPredicate <TestEntity , ?> combined = stringPred .andAny (longPred );
53
+ assertNotNull (combined );
54
+ assertTrue (combined instanceof AndPredicate );
55
+
56
+ // Verify the predicate was added
57
+ AndPredicate <TestEntity , ?> andPred = (AndPredicate <TestEntity , ?>) combined ;
58
+ assertEquals (2 , andPred .stream ().count ());
59
+ }
60
+
61
+ @ Test
62
+ void testIssue342_CanCombineDifferentTypePredicatesWithOrAny () throws NoSuchFieldException {
63
+ // Create field accessors
64
+ Field nameSpaceField = TestEntity .class .getDeclaredField ("nameSpace" );
65
+ Field relateIdField = TestEntity .class .getDeclaredField ("relateId" );
66
+
67
+ SearchFieldAccessor nameSpaceAccessor = new SearchFieldAccessor ("@nameSpace" , "$.nameSpace" , nameSpaceField );
68
+ SearchFieldAccessor relateIdAccessor = new SearchFieldAccessor ("@relateId" , "$.relateId" , relateIdField );
69
+
70
+ // Create predicates with different types
71
+ TagField <TestEntity , String > nameSpacePredicate = new TagField <>(nameSpaceAccessor , true );
72
+ NumericField <TestEntity , Long > relateIdPredicate = new NumericField <>(relateIdAccessor , true );
73
+
74
+ // Test that we can combine String and Long predicates with orAny()
75
+ SearchFieldPredicate <TestEntity , String > stringPred = nameSpacePredicate .eq ("BUSINESS" );
76
+ SearchFieldPredicate <TestEntity , Long > longPred = relateIdPredicate .eq (200L );
77
+
78
+ // This should compile without type errors
79
+ SearchFieldPredicate <TestEntity , ?> combined = stringPred .orAny (longPred );
80
+ assertNotNull (combined );
81
+ assertTrue (combined instanceof OrPredicate );
82
+
83
+ // Verify the predicate was added
84
+ OrPredicate <TestEntity , ?> orPred = (OrPredicate <TestEntity , ?>) combined ;
85
+ assertEquals (2 , orPred .stream ().count ());
86
+ }
87
+
88
+ @ Test
89
+ void testIssue342_ChainMultipleDifferentTypes () throws NoSuchFieldException {
90
+ // Create field accessors
91
+ Field nameSpaceField = TestEntity .class .getDeclaredField ("nameSpace" );
92
+ Field relateIdField = TestEntity .class .getDeclaredField ("relateId" );
93
+ Field statusField = TestEntity .class .getDeclaredField ("status" );
94
+
95
+ SearchFieldAccessor nameSpaceAccessor = new SearchFieldAccessor ("@nameSpace" , "$.nameSpace" , nameSpaceField );
96
+ SearchFieldAccessor relateIdAccessor = new SearchFieldAccessor ("@relateId" , "$.relateId" , relateIdField );
97
+ SearchFieldAccessor statusAccessor = new SearchFieldAccessor ("@status" , "$.status" , statusField );
98
+
99
+ // Create predicates with different types
100
+ TagField <TestEntity , String > nameSpacePredicate = new TagField <>(nameSpaceAccessor , true );
101
+ NumericField <TestEntity , Long > relateIdPredicate = new NumericField <>(relateIdAccessor , true );
102
+ TagField <TestEntity , String > statusPredicate = new TagField <>(statusAccessor , true );
103
+
104
+ // Test chaining multiple different types
105
+ SearchFieldPredicate <TestEntity , ?> combined = nameSpacePredicate .eq ("PERSONAL" )
106
+ .andAny (relateIdPredicate .eq (100L ))
107
+ .andAny (statusPredicate .eq ("ACTIVE" ));
108
+
109
+ assertNotNull (combined );
110
+ assertTrue (combined instanceof AndPredicate );
111
+ }
112
+
113
+ @ Test
114
+ void testIssue342_MixAndAnyAndOrAny () throws NoSuchFieldException {
115
+ // Create field accessors
116
+ Field nameSpaceField = TestEntity .class .getDeclaredField ("nameSpace" );
117
+ Field relateIdField = TestEntity .class .getDeclaredField ("relateId" );
118
+
119
+ SearchFieldAccessor nameSpaceAccessor = new SearchFieldAccessor ("@nameSpace" , "$.nameSpace" , nameSpaceField );
120
+ SearchFieldAccessor relateIdAccessor = new SearchFieldAccessor ("@relateId" , "$.relateId" , relateIdField );
121
+
122
+ // Create predicates with different types
123
+ TagField <TestEntity , String > nameSpacePredicate = new TagField <>(nameSpaceAccessor , true );
124
+ NumericField <TestEntity , Long > relateIdPredicate = new NumericField <>(relateIdAccessor , true );
125
+
126
+ // Test mixing andAny and orAny
127
+ SearchFieldPredicate <TestEntity , ?> combined = nameSpacePredicate .eq ("PERSONAL" )
128
+ .andAny (relateIdPredicate .gt (50L ))
129
+ .orAny (nameSpacePredicate .eq ("BUSINESS" ));
130
+
131
+ assertNotNull (combined );
132
+ assertTrue (combined instanceof OrPredicate );
133
+ }
134
+
135
+ @ Test
136
+ void testIssue342_SameTypeStillWorksWithRegularAnd () throws NoSuchFieldException {
137
+ // Create field accessors for same type
138
+ Field nameSpaceField = TestEntity .class .getDeclaredField ("nameSpace" );
139
+ Field statusField = TestEntity .class .getDeclaredField ("status" );
140
+
141
+ SearchFieldAccessor nameSpaceAccessor = new SearchFieldAccessor ("@nameSpace" , "$.nameSpace" , nameSpaceField );
142
+ SearchFieldAccessor statusAccessor = new SearchFieldAccessor ("@status" , "$.status" , statusField );
143
+
144
+ // Create predicates with same type (String)
145
+ TagField <TestEntity , String > nameSpacePredicate = new TagField <>(nameSpaceAccessor , true );
146
+ TagField <TestEntity , String > statusPredicate = new TagField <>(statusAccessor , true );
147
+
148
+ // Test that same-type predicates still work with regular and()
149
+ // Note: and() returns Predicate<T>, not SearchFieldPredicate, so we use andAny for consistency
150
+ SearchFieldPredicate <TestEntity , ?> combined = nameSpacePredicate .eq ("PERSONAL" )
151
+ .andAny (statusPredicate .eq ("ACTIVE" ));
152
+
153
+ assertNotNull (combined );
154
+ assertTrue (combined instanceof AndPredicate );
155
+
156
+ // Verify both predicates are included
157
+ @ SuppressWarnings ("unchecked" )
158
+ AndPredicate <TestEntity , ?> andPred = (AndPredicate <TestEntity , ?>) combined ;
159
+ assertEquals (2 , andPred .stream ().count ());
160
+ }
161
+ }
0 commit comments