Skip to content

Commit b78f33f

Browse files
committed
feat: add numeric array membership methods to NumericField (#400)
Add containsDouble(), containsLong(), and containsInt() methods to NumericField class to enable array membership queries similar to TagField.in() functionality. These methods allow users to query for entities where numeric array fields contain any of the specified values using EntityStream. Resolves #400 network restrictions when downloading HuggingFace models. Since our test for issue #400 does not require AI functionality, we disable it to prevent the ApplicationContext initialization failure.
1 parent 82f6661 commit b78f33f

File tree

4 files changed

+317
-0
lines changed

4 files changed

+317
-0
lines changed

redis-om-spring/src/main/java/com/redis/om/spring/metamodel/indexed/NumericField.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,49 @@ public Consumer<E> decrBy(Long value) {
163163
return new NumIncrByAction<>(searchFieldAccessor, -value);
164164
}
165165

166+
/**
167+
* Creates an array membership predicate for this numeric field to check if the array contains any of the specified
168+
* Double values.
169+
* This method is similar to TagField.in() but for numeric arrays.
170+
*
171+
* @param values the Double values to check for membership in the array
172+
* @return an InPredicate that matches entities where this numeric array field contains any of the specified values
173+
*/
174+
@SuppressWarnings(
175+
"unchecked"
176+
)
177+
public InPredicate<E, ?> containsDouble(Double... values) {
178+
return new InPredicate<>(searchFieldAccessor, Arrays.asList(values));
179+
}
180+
181+
/**
182+
* Creates an array membership predicate for this numeric field to check if the array contains any of the specified
183+
* Long values.
184+
* This method is similar to TagField.in() but for numeric arrays.
185+
*
186+
* @param values the Long values to check for membership in the array
187+
* @return an InPredicate that matches entities where this numeric array field contains any of the specified values
188+
*/
189+
@SuppressWarnings(
190+
"unchecked"
191+
)
192+
public InPredicate<E, ?> containsLong(Long... values) {
193+
return new InPredicate<>(searchFieldAccessor, Arrays.asList(values));
194+
}
195+
196+
/**
197+
* Creates an array membership predicate for this numeric field to check if the array contains any of the specified
198+
* Integer values.
199+
* This method is similar to TagField.in() but for numeric arrays.
200+
*
201+
* @param values the Integer values to check for membership in the array
202+
* @return an InPredicate that matches entities where this numeric array field contains any of the specified values
203+
*/
204+
@SuppressWarnings(
205+
"unchecked"
206+
)
207+
public InPredicate<E, ?> containsInt(Integer... values) {
208+
return new InPredicate<>(searchFieldAccessor, Arrays.asList(values));
209+
}
210+
166211
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.redis.om.spring.fixtures.document.model;
2+
3+
import java.util.List;
4+
5+
import org.springframework.data.annotation.Id;
6+
7+
import com.redis.om.spring.annotations.Document;
8+
import com.redis.om.spring.annotations.NumericIndexed;
9+
import com.redis.om.spring.annotations.TagIndexed;
10+
11+
import lombok.AccessLevel;
12+
import lombok.AllArgsConstructor;
13+
import lombok.Data;
14+
import lombok.NoArgsConstructor;
15+
16+
/**
17+
* Test model for demonstrating GitHub issue #400:
18+
* NumericField lacks methods to check if a numeric array contains specific numbers
19+
*/
20+
@Data
21+
@NoArgsConstructor
22+
@AllArgsConstructor(access = AccessLevel.PROTECTED)
23+
@Document
24+
public class NumericArrayTestData {
25+
@Id
26+
private String id;
27+
28+
@TagIndexed
29+
private String name;
30+
31+
@NumericIndexed
32+
private List<Double> measurements;
33+
34+
@NumericIndexed
35+
private List<Long> counts;
36+
37+
@NumericIndexed
38+
private List<Integer> ratings;
39+
40+
@TagIndexed
41+
private List<String> tags; // For comparison - this works with TagField.in()
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.redis.om.spring.fixtures.document.repository;
2+
3+
import com.redis.om.spring.fixtures.document.model.NumericArrayTestData;
4+
import com.redis.om.spring.repository.RedisDocumentRepository;
5+
6+
/**
7+
* Repository for NumericArrayTestData to demonstrate GitHub issue #400
8+
*/
9+
public interface NumericArrayTestDataRepository extends RedisDocumentRepository<NumericArrayTestData, String> {
10+
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
package com.redis.om.spring.search.stream;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.junit.jupiter.api.Assertions.*;
5+
6+
import java.util.Arrays;
7+
import java.util.List;
8+
import java.util.stream.Collectors;
9+
10+
import org.junit.jupiter.api.AfterEach;
11+
import org.junit.jupiter.api.BeforeEach;
12+
import org.junit.jupiter.api.Test;
13+
import org.springframework.beans.factory.annotation.Autowired;
14+
15+
import com.redis.om.spring.AbstractBaseDocumentTest;
16+
import com.redis.om.spring.fixtures.document.model.NumericArrayTestData;
17+
import com.redis.om.spring.fixtures.document.model.NumericArrayTestData$;
18+
import com.redis.om.spring.fixtures.document.repository.NumericArrayTestDataRepository;
19+
import com.redis.om.spring.search.stream.EntityStream;
20+
21+
/**
22+
* Test class to demonstrate the issue described in GitHub issue #400:
23+
* https://github.com/redis/redis-om-spring/issues/400
24+
*
25+
* The issue is that NumericField lacks methods to check if a numeric array
26+
* contains specific numbers when using EntityStream, similar to how TagField.in() works.
27+
*
28+
* This test proves that the functionality is missing and shows what should work
29+
* once the feature is implemented.
30+
*/
31+
class NumericArraySearchTest extends AbstractBaseDocumentTest {
32+
33+
@Autowired
34+
NumericArrayTestDataRepository repository;
35+
36+
@Autowired
37+
EntityStream entityStream;
38+
39+
private NumericArrayTestData testData1;
40+
private NumericArrayTestData testData2;
41+
private NumericArrayTestData testData3;
42+
43+
@BeforeEach
44+
void loadTestData() {
45+
testData1 = new NumericArrayTestData();
46+
testData1.setName("data1");
47+
testData1.setMeasurements(Arrays.asList(1.5, 2.5, 3.5));
48+
testData1.setCounts(Arrays.asList(10L, 20L, 30L));
49+
testData1.setRatings(Arrays.asList(1, 2, 3));
50+
testData1.setTags(Arrays.asList("tag1", "tag2"));
51+
52+
testData2 = new NumericArrayTestData();
53+
testData2.setName("data2");
54+
testData2.setMeasurements(Arrays.asList(2.5, 4.5, 6.5));
55+
testData2.setCounts(Arrays.asList(20L, 40L, 60L));
56+
testData2.setRatings(Arrays.asList(2, 4, 6));
57+
testData2.setTags(Arrays.asList("tag2", "tag3"));
58+
59+
testData3 = new NumericArrayTestData();
60+
testData3.setName("data3");
61+
testData3.setMeasurements(Arrays.asList(3.5, 7.5, 9.5));
62+
testData3.setCounts(Arrays.asList(30L, 70L, 90L));
63+
testData3.setRatings(Arrays.asList(3, 7, 9));
64+
testData3.setTags(Arrays.asList("tag1", "tag4"));
65+
66+
repository.saveAll(Arrays.asList(testData1, testData2, testData3));
67+
}
68+
69+
@AfterEach
70+
void cleanUp() {
71+
repository.deleteAll();
72+
}
73+
74+
/**
75+
* This test shows that TagField.in() works perfectly for string arrays with EntityStream.
76+
* This is the pattern that NumericField should follow for numeric arrays.
77+
*/
78+
@Test
79+
void testTagFieldInWorksWithEntityStream() {
80+
// This demonstrates TagField.in() working with EntityStream:
81+
List<NumericArrayTestData> results = entityStream
82+
.of(NumericArrayTestData.class)
83+
.filter(NumericArrayTestData$.TAGS.in("tag1", "tag3"))
84+
.collect(Collectors.toList());
85+
86+
// Verify the results - should find testData1 and testData3 (both have "tag1") and testData2 (has "tag3")
87+
assertThat(results).hasSize(3);
88+
assertThat(results).contains(testData1, testData2, testData3);
89+
}
90+
91+
/**
92+
* This test demonstrates the MISSING functionality for numeric arrays.
93+
* This is the core issue from GitHub #400.
94+
*/
95+
@Test
96+
void testNumericFieldLacksContainsFunctionalityWithEntityStream() {
97+
// Test basic data setup first
98+
assertThat(testData1.getMeasurements()).contains(2.5);
99+
assertThat(testData2.getMeasurements()).contains(2.5);
100+
assertThat(testData3.getMeasurements()).contains(7.5);
101+
102+
// Should be able to find entities where measurements contains any of these values
103+
List<NumericArrayTestData> measurementResults = entityStream
104+
.of(NumericArrayTestData.class)
105+
.filter(NumericArrayTestData$.MEASUREMENTS.containsDouble(2.5, 7.5))
106+
.collect(Collectors.toList());
107+
assertThat(measurementResults).hasSize(3); // All entities have at least one of these values
108+
109+
// Should be able to find entities where counts contains any of these values
110+
List<NumericArrayTestData> countResults = entityStream
111+
.of(NumericArrayTestData.class)
112+
.filter(NumericArrayTestData$.COUNTS.containsLong(20L, 70L))
113+
.collect(Collectors.toList());
114+
assertThat(countResults).hasSize(3); // All entities have at least one of these values
115+
116+
// Should be able to find entities where ratings contains any of these values
117+
List<NumericArrayTestData> ratingResults = entityStream
118+
.of(NumericArrayTestData.class)
119+
.filter(NumericArrayTestData$.RATINGS.containsInt(2, 7))
120+
.collect(Collectors.toList());
121+
assertThat(ratingResults).hasSize(3); // testData1 has 2, testData2 has 2, testData3 has 7
122+
}
123+
124+
/**
125+
* This test shows what currently happens when you try to use the existing in() method
126+
* on NumericField with EntityStream - it doesn't work as expected for arrays.
127+
*/
128+
@Test
129+
void testCurrentNumericFieldInMethodLimitation() {
130+
// The current in() method on NumericField works for scalar matching, not array membership
131+
// This is different from TagField.in() which works for array membership
132+
133+
// The current NumericField.in() method expects List<T> not individual values
134+
// This shows the fundamental difference from TagField.in() which accepts varargs
135+
136+
// This would fail to compile: TestData$.MEASUREMENTS.in(2.5)
137+
// because NumericField.in() expects List<Double>, not double
138+
139+
// NumericField.in() signature: in(List<Double> values)
140+
// TagField.in() signature: in(String... values) or in(Object... values)
141+
142+
// The current NumericField.in() method would require the metamodel:
143+
// List<NumericArrayTestData> results = entityStream
144+
// .of(NumericArrayTestData.class)
145+
// .filter(NumericArrayTestData$.MEASUREMENTS.in(Arrays.asList(2.5))) // Must pass as List
146+
// .collect(Collectors.toList());
147+
148+
// Demonstrate the data exists but we can't query it effectively
149+
assertThat(testData1.getMeasurements()).contains(2.5);
150+
assertThat(testData2.getMeasurements()).contains(2.5);
151+
152+
// The issue: no easy way to find entities where numeric array contains any of multiple values
153+
154+
// But even if this worked, it doesn't give us "array contains any of these values" semantics
155+
// It's checking if the field value equals the provided list, not membership
156+
assertTrue(true, "Current NumericField.in() doesn't support array membership like TagField.in() does");
157+
158+
// The key issue: NumericField.in() ≠ TagField.in() for array membership behavior
159+
assertTrue(true, "NumericField.in() doesn't support array membership like TagField.in() does");
160+
}
161+
162+
/**
163+
* This test demonstrates the exact scenario from GitHub issue #400.
164+
* User wants to query: "Find all entities where numeric array contains any of these values"
165+
*/
166+
@Test
167+
void testGitHubIssue400Scenario() {
168+
// Issue #400 specifically asks for functionality like:
169+
// field.containsLong(Long... values) similar to TagField.in()
170+
171+
// Verify test data setup
172+
assertThat(testData1.getCounts()).contains(20L);
173+
assertThat(testData2.getCounts()).contains(20L);
174+
assertThat(testData3.getCounts()).contains(70L);
175+
176+
// This should now work with the new containsLong method:
177+
List<NumericArrayTestData> results = entityStream
178+
.of(NumericArrayTestData.class)
179+
.filter(NumericArrayTestData$.COUNTS.containsLong(20L, 70L))
180+
.collect(Collectors.toList());
181+
182+
// Should find testData1 (has 20L), testData2 (has 20L), and testData3 (has 70L)
183+
assertThat(results).hasSize(3);
184+
assertThat(results).contains(testData1, testData2, testData3);
185+
}
186+
187+
/**
188+
* This test proves the issue exists by showing the compilation failure.
189+
* When the feature is implemented, this test should be updated to verify the functionality works.
190+
*/
191+
@Test
192+
void testCompilationFailureProvesIssueExists() {
193+
// Verify test data has the expected values
194+
assertThat(testData1.getMeasurements()).containsAnyOf(1.5, 4.5, 7.5);
195+
assertThat(testData1.getCounts()).containsAnyOf(10L, 40L, 70L);
196+
assertThat(testData1.getRatings()).containsAnyOf(1, 4, 7);
197+
198+
// Now these methods should work and compile successfully:
199+
200+
List<NumericArrayTestData> measurementResults = entityStream
201+
.of(NumericArrayTestData.class)
202+
.filter(NumericArrayTestData$.MEASUREMENTS.containsDouble(1.5, 4.5, 7.5))
203+
.collect(Collectors.toList());
204+
205+
List<NumericArrayTestData> countResults = entityStream
206+
.of(NumericArrayTestData.class)
207+
.filter(NumericArrayTestData$.COUNTS.containsLong(10L, 40L, 70L))
208+
.collect(Collectors.toList());
209+
210+
List<NumericArrayTestData> ratingResults = entityStream
211+
.of(NumericArrayTestData.class)
212+
.filter(NumericArrayTestData$.RATINGS.containsInt(1, 4, 7))
213+
.collect(Collectors.toList());
214+
215+
// Verify the methods work as expected
216+
assertThat(measurementResults).hasSize(3); // All entities have at least one of these measurements
217+
assertThat(countResults).hasSize(3); // All entities have at least one of these counts
218+
assertThat(ratingResults).hasSize(3); // All entities have at least one of these ratings
219+
}
220+
}

0 commit comments

Comments
 (0)