SearchWithParamsCommandsTestBase.java
package redis.clients.jedis.commands.unified.search;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static redis.clients.jedis.util.AssertUtil.assertOK;
import static redis.clients.jedis.util.RedisConditions.ModuleVersion.SEARCH_MOD_VER_80M3;
import java.util.*;
import java.util.stream.Collectors;
import io.redis.test.annotations.SinceRedisVersion;
import io.redis.test.utils.RedisVersion;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Tag;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import redis.clients.jedis.Endpoints;
import redis.clients.jedis.GeoCoordinate;
import redis.clients.jedis.RedisProtocol;
import redis.clients.jedis.args.GeoUnit;
import redis.clients.jedis.args.SortingOrder;
import redis.clients.jedis.commands.unified.UnifiedJedisCommandsTestBase;
import redis.clients.jedis.exceptions.JedisDataException;
import redis.clients.jedis.json.Path;
import redis.clients.jedis.search.*;
import redis.clients.jedis.search.RediSearchUtil;
import redis.clients.jedis.search.schemafields.*;
import redis.clients.jedis.search.schemafields.GeoShapeField.CoordinateSystem;
import redis.clients.jedis.search.schemafields.VectorField.VectorAlgorithm;
import redis.clients.jedis.util.RedisConditions;
import redis.clients.jedis.util.RedisVersionUtil;
/**
* Base test class for Search commands with params using the UnifiedJedis pattern.
*/
@Tag("search")
public abstract class SearchWithParamsCommandsTestBase extends UnifiedJedisCommandsTestBase {
protected static final String INDEX = "testindex";
@BeforeAll
public static void prepareEndpoint() {
endpoint = Endpoints.getRedisEndpoint("modules-docker");
}
public SearchWithParamsCommandsTestBase(RedisProtocol protocol) {
super(protocol);
}
@AfterEach
public void cleanUp() {
if (jedis.ftList().contains(INDEX)) {
jedis.ftDropIndex(INDEX);
}
}
protected void addDocument(String key, Map<String, Object> map) {
jedis.hset(key, RediSearchUtil.toStringMap(map));
}
protected static Map<String, Object> toMap(Object... values) {
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < values.length; i += 2) {
map.put((String) values[i], values[i + 1]);
}
return map;
}
protected static Map<String, String> toStringMap(String... values) {
Map<String, String> map = new HashMap<>();
for (int i = 0; i < values.length; i += 2) {
map.put(values[i], values[i + 1]);
}
return map;
}
@Test
public void create() {
assertOK(jedis.ftCreate(INDEX,
FTCreateParams.createParams().filter("@age>16").prefix("student:", "pupil:"),
TextField.of("first"), TextField.of("last"), NumericField.of("age")));
jedis.hset("profesor:5555", toStringMap("first", "Albert", "last", "Blue", "age", "55"));
jedis.hset("student:1111", toStringMap("first", "Joe", "last", "Dod", "age", "18"));
jedis.hset("pupil:2222", toStringMap("first", "Jen", "last", "Rod", "age", "14"));
jedis.hset("student:3333", toStringMap("first", "El", "last", "Mark", "age", "17"));
jedis.hset("pupil:4444", toStringMap("first", "Pat", "last", "Shu", "age", "21"));
jedis.hset("student:5555", toStringMap("first", "Joen", "last", "Ko", "age", "20"));
jedis.hset("teacher:6666", toStringMap("first", "Pat", "last", "Rod", "age", "20"));
SearchResult noFilters = jedis.ftSearch(INDEX);
assertEquals(4, noFilters.getTotalResults());
SearchResult res1 = jedis.ftSearch(INDEX, "@first:Jo*");
assertEquals(2, res1.getTotalResults());
SearchResult res2 = jedis.ftSearch(INDEX, "@first:Pat");
assertEquals(1, res2.getTotalResults());
SearchResult res3 = jedis.ftSearch(INDEX, "@last:Rod");
assertEquals(0, res3.getTotalResults());
}
@Test
public void createNoParams() {
assertOK(jedis.ftCreate(INDEX, TextField.of("first").weight(1), TextField.of("last").weight(1),
NumericField.of("age")));
addDocument("student:1111", toMap("first", "Joe", "last", "Dod", "age", 18));
addDocument("student:3333", toMap("first", "El", "last", "Mark", "age", 17));
addDocument("pupil:4444", toMap("first", "Pat", "last", "Shu", "age", 21));
addDocument("student:5555", toMap("first", "Joen", "last", "Ko", "age", 20));
SearchResult noFilters = jedis.ftSearch(INDEX);
assertEquals(4, noFilters.getTotalResults());
SearchResult res1 = jedis.ftSearch(INDEX, "@first:Jo*");
assertEquals(2, res1.getTotalResults());
SearchResult res2 = jedis.ftSearch(INDEX, "@first:Pat");
assertEquals(1, res2.getTotalResults());
SearchResult res3 = jedis.ftSearch(INDEX, "@last:Rod");
assertEquals(0, res3.getTotalResults());
}
@Test
public void createWithFieldNames() {
assertOK(jedis.ftCreate(INDEX, FTCreateParams.createParams().prefix("student:", "pupil:"),
TextField.of(FieldName.of("first").as("given")), TextField.of("last")));
jedis.hset("profesor:5555", toStringMap("first", "Albert", "last", "Blue", "age", "55"));
jedis.hset("student:1111", toStringMap("first", "Joe", "last", "Dod", "age", "18"));
jedis.hset("pupil:2222", toStringMap("first", "Jen", "last", "Rod", "age", "14"));
jedis.hset("student:3333", toStringMap("first", "El", "last", "Mark", "age", "17"));
jedis.hset("pupil:4444", toStringMap("first", "Pat", "last", "Shu", "age", "21"));
jedis.hset("student:5555", toStringMap("first", "Joen", "last", "Ko", "age", "20"));
jedis.hset("teacher:6666", toStringMap("first", "Pat", "last", "Rod", "age", "20"));
SearchResult noFilters = jedis.ftSearch(INDEX);
assertEquals(5, noFilters.getTotalResults());
SearchResult asAttribute = jedis.ftSearch(INDEX, "@given:Jo*");
assertEquals(2, asAttribute.getTotalResults());
SearchResult nonAttribute = jedis.ftSearch(INDEX, "@last:Rod");
assertEquals(1, nonAttribute.getTotalResults());
}
@Test
public void alterAdd() {
assertOK(jedis.ftCreate(INDEX, TextField.of("title")));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
for (int i = 0; i < 100; i++) {
addDocument(String.format("doc%d", i), fields);
}
SearchResult res = jedis.ftSearch(INDEX, "hello world");
assertEquals(100, res.getTotalResults());
assertOK(jedis.ftAlter(INDEX, TagField.of("tags"), TextField.of("name").weight(0.5)));
for (int i = 0; i < 100; i++) {
Map<String, Object> fields2 = new HashMap<>();
fields2.put("name", "name" + i);
fields2.put("tags", String.format("tagA,tagB,tag%d", i));
addDocument(String.format("doc%d", i), fields2);
}
SearchResult res2 = jedis.ftSearch(INDEX, "@tags:{tagA}");
assertEquals(100, res2.getTotalResults());
}
@Test
public void search() {
assertOK(jedis.ftCreate(INDEX, FTCreateParams.createParams(), TextField.of("title"),
TextField.of("body")));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
fields.put("body", "lorem ipsum");
for (int i = 0; i < 100; i++) {
addDocument(String.format("doc%d", i), fields);
}
SearchResult result = jedis.ftSearch(INDEX, "hello world",
FTSearchParams.searchParams().limit(0, 5).withScores());
assertEquals(100, result.getTotalResults());
assertEquals(5, result.getDocuments().size());
for (Document d : result.getDocuments()) {
assertTrue(d.getId().startsWith("doc"));
assertTrue(d.getScore() < 100);
}
jedis.del("doc0");
result = jedis.ftSearch(INDEX, "hello world");
assertEquals(99, result.getTotalResults());
assertEquals("OK", jedis.ftDropIndex(INDEX));
try {
jedis.ftSearch(INDEX, "hello world");
fail();
} catch (JedisDataException e) {
}
}
@Test
public void numericFilter() {
assertOK(jedis.ftCreate(INDEX, TextField.of("title"), NumericField.of("price")));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
for (int i = 0; i < 100; i++) {
fields.put("price", i);
addDocument(String.format("doc%d", i), fields);
}
SearchResult res = jedis.ftSearch(INDEX, "hello world",
FTSearchParams.searchParams().filter("price", 0, 49));
assertEquals(50, res.getTotalResults());
assertEquals(10, res.getDocuments().size());
for (Document d : res.getDocuments()) {
long price = Long.valueOf((String) d.get("price"));
assertTrue(price >= 0);
assertTrue(price <= 49);
}
res = jedis.ftSearch(INDEX, "hello world",
FTSearchParams.searchParams().filter("price", 0, true, 49, true));
assertEquals(48, res.getTotalResults());
assertEquals(10, res.getDocuments().size());
for (Document d : res.getDocuments()) {
long price = Long.valueOf((String) d.get("price"));
assertTrue(price > 0);
assertTrue(price < 49);
}
res = jedis.ftSearch(INDEX, "hello world",
FTSearchParams.searchParams().filter("price", 50, 100));
assertEquals(50, res.getTotalResults());
assertEquals(10, res.getDocuments().size());
for (Document d : res.getDocuments()) {
long price = Long.valueOf((String) d.get("price"));
assertTrue(price >= 50);
assertTrue(price <= 100);
}
res = jedis.ftSearch(INDEX, "hello world",
FTSearchParams.searchParams().filter("price", 20, Double.POSITIVE_INFINITY));
assertEquals(80, res.getTotalResults());
assertEquals(10, res.getDocuments().size());
res = jedis.ftSearch(INDEX, "hello world",
FTSearchParams.searchParams().filter("price", Double.NEGATIVE_INFINITY, 10));
assertEquals(11, res.getTotalResults());
assertEquals(10, res.getDocuments().size());
}
@Test
public void noStopwords() {
assertOK(
jedis.ftCreate(INDEX, FTCreateParams.createParams().noStopwords(), TextField.of("title")));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world foo bar to be or not to be");
addDocument("doc1", fields);
assertEquals(1, jedis.ftSearch(INDEX, "hello world").getTotalResults());
assertEquals(1, jedis.ftSearch(INDEX, "foo bar").getTotalResults());
assertEquals(1, jedis.ftSearch(INDEX, "to be or not to be").getTotalResults());
}
@Test
public void geoFilter() {
assertOK(jedis.ftCreate(INDEX, TextField.of("title"), GeoField.of("loc")));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
fields.put("loc", "-0.441,51.458");
addDocument("doc1", fields);
fields.put("loc", "-0.1,51.2");
addDocument("doc2", fields);
SearchResult res = jedis.ftSearch(INDEX, "hello world",
FTSearchParams.searchParams().geoFilter("loc", -0.44, 51.45, 10, GeoUnit.KM));
assertEquals(1, res.getTotalResults());
res = jedis.ftSearch(INDEX, "hello world",
FTSearchParams.searchParams().geoFilter("loc", -0.44, 51.45, 100, GeoUnit.KM));
assertEquals(2, res.getTotalResults());
}
@Test
public void dropIndex() {
assertOK(jedis.ftCreate(INDEX, TextField.of("title")));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
for (int i = 0; i < 100; i++) {
addDocument(String.format("doc%d", i), fields);
}
SearchResult res = jedis.ftSearch(INDEX, "hello world");
assertEquals(100, res.getTotalResults());
assertEquals("OK", jedis.ftDropIndex(INDEX));
try {
jedis.ftSearch(INDEX, "hello world");
fail("Index should not exist.");
} catch (JedisDataException de) {
assertTrue(de.getMessage().toLowerCase().contains("no such index"));
}
assertEquals(100, jedis.dbSize());
}
@Test
public void dropIndexDD() {
assertOK(jedis.ftCreate(INDEX, TextField.of("title")));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
for (int i = 0; i < 100; i++) {
addDocument(String.format("doc%d", i), fields);
}
SearchResult res = jedis.ftSearch(INDEX, "hello world");
assertEquals(100, res.getTotalResults());
assertEquals("OK", jedis.ftDropIndexDD(INDEX));
Set<String> keys = jedis.keys("*");
assertTrue(keys.isEmpty());
assertEquals(0, jedis.dbSize());
}
@Test
public void alias() {
assertOK(jedis.ftCreate(INDEX, TextField.of("field1")));
Map<String, Object> doc = new HashMap<>();
doc.put("field1", "value");
addDocument("doc1", doc);
assertOK(jedis.ftAliasAdd("ALIAS1", INDEX));
SearchResult res1 = jedis.ftSearch("ALIAS1", "*",
FTSearchParams.searchParams().returnFields("field1"));
assertEquals(1, res1.getTotalResults());
assertEquals("value", res1.getDocuments().get(0).get("field1"));
assertOK(jedis.ftAliasUpdate("ALIAS2", INDEX));
SearchResult res2 = jedis.ftSearch("ALIAS2", "*",
FTSearchParams.searchParams().returnFields("field1"));
assertEquals(1, res2.getTotalResults());
assertEquals("value", res2.getDocuments().get(0).get("field1"));
assertThrows(JedisDataException.class, () -> jedis.ftAliasDel("ALIAS3"));
assertOK(jedis.ftAliasDel("ALIAS2"));
assertThrows(JedisDataException.class, () -> jedis.ftAliasDel("ALIAS2"));
}
@Test
public void synonym() {
assertOK(jedis.ftCreate(INDEX, TextField.of("name"), TextField.of("addr")));
long group1 = 345L;
long group2 = 789L;
String group1_str = Long.toString(group1);
String group2_str = Long.toString(group2);
assertOK(jedis.ftSynUpdate(INDEX, group1_str, "girl", "baby"));
assertOK(jedis.ftSynUpdate(INDEX, group1_str, "child"));
assertOK(jedis.ftSynUpdate(INDEX, group2_str, "child"));
Map<String, List<String>> dump = jedis.ftSynDump(INDEX);
Map<String, List<String>> expected = new HashMap<>();
expected.put("girl", Arrays.asList(group1_str));
expected.put("baby", Arrays.asList(group1_str));
expected.put("child", Arrays.asList(group1_str, group2_str));
assertEquals(expected, dump);
}
@Test
public void ftExplain() {
assertOK(jedis.ftCreate(INDEX, TextField.of("f1"), TextField.of("f2"), TextField.of("f3")));
String res = jedis.ftExplain(INDEX, new Query("@f3:f3_val @f2:f2_val @f1:f1_val"));
assertThat(res, Matchers.not(Matchers.emptyOrNullString()));
}
@Test
public void ftList() {
assertOK(jedis.ftCreate(INDEX, TextField.of("field1")));
Set<String> indices = jedis.ftList();
assertTrue(indices.contains(INDEX));
}
@Test
public void textFieldParams() {
assertOK(jedis.ftCreate("testindex",
TextField.of("title").weight(2.5).noStem().phonetic("dm:en").withSuffixTrie().sortable()));
assertOK(jedis.ftCreate("testunfindex",
TextField.of("title").weight(2.5).noStem().phonetic("dm:en").withSuffixTrie().sortableUNF()));
if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {
assertOK(jedis.ftCreate("testindex-missing", TextField.of("title").indexMissing().indexEmpty()
.weight(2.5).noStem().phonetic("dm:en").withSuffixTrie().sortable()));
assertOK(jedis.ftCreate("testunfindex-missing", TextField.of("title").indexMissing()
.indexEmpty().weight(2.5).noStem().phonetic("dm:en").withSuffixTrie().sortableUNF()));
}
assertOK(jedis.ftCreate("testnoindex", TextField.of("title").sortable().noIndex()));
assertOK(jedis.ftCreate("testunfnoindex", TextField.of("title").sortableUNF().noIndex()));
}
@Test
@SinceRedisVersion(value = "7.4.0", message = "optional params since 7.4.0 are being tested")
public void searchTextFieldsCondition() {
assertOK(jedis.ftCreate(INDEX, FTCreateParams.createParams(), TextField.of("title"),
TextField.of("body").indexMissing().indexEmpty()));
Map<String, String> regular = new HashMap<>();
regular.put("title", "hello world");
regular.put("body", "lorem ipsum");
jedis.hset("regular-doc", regular);
Map<String, String> empty = new HashMap<>();
empty.put("title", "hello world");
empty.put("body", "");
jedis.hset("empty-doc", empty);
Map<String, String> missing = new HashMap<>();
missing.put("title", "hello world");
jedis.hset("missing-doc", missing);
SearchResult result = jedis.ftSearch(INDEX, "", FTSearchParams.searchParams().dialect(2));
assertEquals(0, result.getTotalResults());
assertEquals(0, result.getDocuments().size());
result = jedis.ftSearch(INDEX, "*", FTSearchParams.searchParams().dialect(2));
assertEquals(3, result.getTotalResults());
assertEquals(3, result.getDocuments().size());
result = jedis.ftSearch(INDEX, "@body:''", FTSearchParams.searchParams().dialect(2));
assertEquals(1, result.getTotalResults());
assertEquals(1, result.getDocuments().size());
assertEquals("empty-doc", result.getDocuments().get(0).getId());
result = jedis.ftSearch(INDEX, "ismissing(@body)", FTSearchParams.searchParams().dialect(2));
assertEquals(1, result.getTotalResults());
assertEquals(1, result.getDocuments().size());
assertEquals("missing-doc", result.getDocuments().get(0).getId());
}
@Test
public void numericFieldParams() {
assertOK(jedis.ftCreate("testindex", TextField.of("title"),
NumericField.of("price").as("px").sortable()));
if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {
assertOK(jedis.ftCreate("testindex-missing", TextField.of("title"),
NumericField.of("price").as("px").indexMissing().sortable()));
}
assertOK(jedis.ftCreate("testnoindex", TextField.of("title"),
NumericField.of("price").as("px").sortable().noIndex()));
}
@Test
public void stopwords() {
assertOK(jedis.ftCreate(INDEX, FTCreateParams.createParams().stopwords("foo", "bar", "baz"),
TextField.of("title")));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world foo bar");
addDocument("doc1", fields);
SearchResult res = jedis.ftSearch(INDEX, "hello world");
assertEquals(1, res.getTotalResults());
res = jedis.ftSearch(INDEX, "foo bar");
assertEquals(0, res.getTotalResults());
}
@Test
public void geoFilterAndGeoCoordinateObject() {
assertOK(jedis.ftCreate(INDEX, TextField.of("title"), GeoField.of("loc")));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
fields.put("loc", new GeoCoordinate(-0.441, 51.458));
addDocument("doc1", fields);
fields.put("loc", new GeoCoordinate(-0.1, 51.2));
addDocument("doc2", fields);
SearchResult res = jedis.ftSearch(INDEX, "hello world", FTSearchParams.searchParams()
.geoFilter(new FTSearchParams.GeoFilter("loc", -0.44, 51.45, 10, GeoUnit.KM)));
assertEquals(1, res.getTotalResults());
res = jedis.ftSearch(INDEX, "hello world", FTSearchParams.searchParams()
.geoFilter(new FTSearchParams.GeoFilter("loc", -0.44, 51.45, 100, GeoUnit.KM)));
assertEquals(2, res.getTotalResults());
}
@Test
public void geoFieldParams() {
assertOK(jedis.ftCreate("testindex", TextField.of("title"),
GeoField.of("location").as("loc").sortable()));
if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {
assertOK(jedis.ftCreate("testindex-missing", TextField.of("title"),
GeoField.of("location").as("loc").indexMissing().sortable()));
}
assertOK(jedis.ftCreate("testnoindex", TextField.of("title"),
GeoField.of("location").as("loc").sortable().noIndex()));
}
@Test
public void geoShapeFilterSpherical() throws ParseException {
final WKTReader reader = new WKTReader();
final GeometryFactory factory = new GeometryFactory();
assertOK(jedis.ftCreate(INDEX, GeoShapeField.of("geom", CoordinateSystem.SPHERICAL)));
// polygon type
final Polygon small = factory.createPolygon(new Coordinate[] { new Coordinate(34.9001, 29.7001),
new Coordinate(34.9001, 29.7100), new Coordinate(34.9100, 29.7100),
new Coordinate(34.9100, 29.7001), new Coordinate(34.9001, 29.7001) });
jedis.hsetObject("small", "geom", small);
final Polygon large = factory.createPolygon(new Coordinate[] { new Coordinate(34.9001, 29.7001),
new Coordinate(34.9001, 29.7200), new Coordinate(34.9200, 29.7200),
new Coordinate(34.9200, 29.7001), new Coordinate(34.9001, 29.7001) });
jedis.hsetObject("large", "geom", large);
// within condition
final Polygon within = factory
.createPolygon(new Coordinate[] { new Coordinate(34.9000, 29.7000),
new Coordinate(34.9000, 29.7150), new Coordinate(34.9150, 29.7150),
new Coordinate(34.9150, 29.7000), new Coordinate(34.9000, 29.7000) });
SearchResult result = jedis.ftSearch(INDEX, "@geom:[within $poly]",
FTSearchParams.searchParams().addParam("poly", within).dialect(3));
assertEquals(1, result.getTotalResults());
assertEquals(1, result.getDocuments().size());
assertEquals(small, reader.read(result.getDocuments().get(0).getString("geom")));
// contains condition
final Polygon contains = factory
.createPolygon(new Coordinate[] { new Coordinate(34.9002, 29.7002),
new Coordinate(34.9002, 29.7050), new Coordinate(34.9050, 29.7050),
new Coordinate(34.9050, 29.7002), new Coordinate(34.9002, 29.7002) });
result = jedis.ftSearch(INDEX, "@geom:[contains $poly]",
FTSearchParams.searchParams().addParam("poly", contains).dialect(3));
assertEquals(2, result.getTotalResults());
assertEquals(2, result.getDocuments().size());
// point type
final Point point = factory.createPoint(new Coordinate(34.9010, 29.7010));
jedis.hset("point", "geom", point.toString());
result = jedis.ftSearch(INDEX, "@geom:[within $poly]",
FTSearchParams.searchParams().addParam("poly", within).dialect(3));
assertEquals(2, result.getTotalResults());
assertEquals(2, result.getDocuments().size());
}
@Test
public void geoShapeFilterFlat() throws ParseException {
final WKTReader reader = new WKTReader();
final GeometryFactory factory = new GeometryFactory();
assertOK(jedis.ftCreate(INDEX, GeoShapeField.of("geom", CoordinateSystem.FLAT)));
// polygon type
final Polygon small = factory
.createPolygon(new Coordinate[] { new Coordinate(20, 20), new Coordinate(20, 100),
new Coordinate(100, 100), new Coordinate(100, 20), new Coordinate(20, 20) });
jedis.hsetObject("small", "geom", small);
final Polygon large = factory
.createPolygon(new Coordinate[] { new Coordinate(10, 10), new Coordinate(10, 200),
new Coordinate(200, 200), new Coordinate(200, 10), new Coordinate(10, 10) });
jedis.hsetObject("large", "geom", large);
// within condition
final Polygon within = factory
.createPolygon(new Coordinate[] { new Coordinate(0, 0), new Coordinate(0, 150),
new Coordinate(150, 150), new Coordinate(150, 0), new Coordinate(0, 0) });
SearchResult result = jedis.ftSearch(INDEX, "@geom:[within $poly]",
FTSearchParams.searchParams().addParam("poly", within).dialect(3));
assertEquals(1, result.getTotalResults());
assertEquals(1, result.getDocuments().size());
assertEquals(small, reader.read(result.getDocuments().get(0).getString("geom")));
// contains condition
final Polygon contains = factory
.createPolygon(new Coordinate[] { new Coordinate(25, 25), new Coordinate(25, 50),
new Coordinate(50, 50), new Coordinate(50, 25), new Coordinate(25, 25) });
result = jedis.ftSearch(INDEX, "@geom:[contains $poly]",
FTSearchParams.searchParams().addParam("poly", contains).dialect(3));
assertEquals(2, result.getTotalResults());
assertEquals(2, result.getDocuments().size());
// intersects and disjoint
if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {
final Polygon disjointersect = factory
.createPolygon(new Coordinate[] { new Coordinate(150, 150), new Coordinate(150, 250),
new Coordinate(250, 250), new Coordinate(250, 150), new Coordinate(150, 150) });
result = jedis.ftSearch(INDEX, "@geom:[intersects $poly]",
FTSearchParams.searchParams().addParam("poly", disjointersect).dialect(3));
assertEquals(1, result.getTotalResults());
assertEquals(1, result.getDocuments().size());
assertEquals(large, reader.read(result.getDocuments().get(0).getString("geom")));
result = jedis.ftSearch(INDEX, "@geom:[disjoint $poly]",
FTSearchParams.searchParams().addParam("poly", disjointersect).dialect(3));
assertEquals(1, result.getTotalResults());
assertEquals(1, result.getDocuments().size());
assertEquals(small, reader.read(result.getDocuments().get(0).getString("geom")));
}
// point type
final Point point = factory.createPoint(new Coordinate(30, 30));
jedis.hsetObject("point", "geom", point);
result = jedis.ftSearch(INDEX, "@geom:[within $poly]",
FTSearchParams.searchParams().addParam("poly", within).dialect(3));
assertEquals(2, result.getTotalResults());
assertEquals(2, result.getDocuments().size());
}
@Test
public void geoShapeFieldParams() {
if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {
assertOK(jedis.ftCreate("testindex-missing",
GeoShapeField.of("geometry", CoordinateSystem.SPHERICAL).as("geom").indexMissing()));
}
assertOK(jedis.ftCreate("testnoindex",
GeoShapeField.of("geometry", CoordinateSystem.SPHERICAL).as("geom").noIndex()));
}
@Test
public void testQueryFlags() {
assertOK(jedis.ftCreate(INDEX, TextField.of("title")));
Map<String, Object> fields = new HashMap<>();
for (int i = 0; i < 100; i++) {
fields.put("title", i % 2 != 0 ? "hello worlds" : "hello world");
addDocument(String.format("doc%d", i), fields);
}
SearchResult res = jedis.ftSearch(INDEX, "hello", FTSearchParams.searchParams().withScores());
assertEquals(100, res.getTotalResults());
assertEquals(10, res.getDocuments().size());
for (Document d : res.getDocuments()) {
assertTrue(d.getId().startsWith("doc"));
assertTrue(((String) d.get("title")).startsWith("hello world"));
}
res = jedis.ftSearch(INDEX, "hello", FTSearchParams.searchParams().noContent());
for (Document d : res.getDocuments()) {
assertTrue(d.getId().startsWith("doc"));
if (protocol != RedisProtocol.RESP3) {
assertEquals(1.0, d.getScore(), 0);
assertNull(d.get("title"));
} else {
assertNull(d.getScore());
assertThrows(NullPointerException.class, () -> d.get("title"));
}
}
// test verbatim vs. stemming
res = jedis.ftSearch(INDEX, "hello worlds");
assertEquals(100, res.getTotalResults());
res = jedis.ftSearch(INDEX, "hello worlds", FTSearchParams.searchParams().verbatim());
assertEquals(50, res.getTotalResults());
res = jedis.ftSearch(INDEX, "hello a world", FTSearchParams.searchParams().verbatim());
assertEquals(50, res.getTotalResults());
res = jedis.ftSearch(INDEX, "hello a worlds", FTSearchParams.searchParams().verbatim());
assertEquals(50, res.getTotalResults());
res = jedis.ftSearch(INDEX, "hello a world",
FTSearchParams.searchParams().verbatim().noStopwords());
assertEquals(0, res.getTotalResults());
}
@Test
public void testQueryParams() {
assertOK(jedis.ftCreate(INDEX, NumericField.of("numval")));
jedis.hset("1", "numval", "1");
jedis.hset("2", "numval", "2");
jedis.hset("3", "numval", "3");
assertEquals(2,
jedis
.ftSearch(INDEX, "@numval:[$min $max]",
FTSearchParams.searchParams().addParam("min", 1).addParam("max", 2).dialect(2))
.getTotalResults());
Map<String, Object> paramValues = new HashMap<>();
paramValues.put("min", 1);
paramValues.put("max", 2);
assertEquals(2, jedis.ftSearch(INDEX, "@numval:[$min $max]",
FTSearchParams.searchParams().params(paramValues).dialect(2)).getTotalResults());
if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {
assertEquals(1, jedis.ftSearch(INDEX, "@numval:[$eq]",
FTSearchParams.searchParams().addParam("eq", 2).dialect(4)).getTotalResults());
}
}
@Test
public void testSortQueryFlags() {
assertOK(jedis.ftCreate(INDEX, TextField.of("title").sortable()));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "b title");
addDocument("doc1", fields);
fields.put("title", "a title");
addDocument("doc2", fields);
fields.put("title", "c title");
addDocument("doc3", fields);
SearchResult res = jedis.ftSearch(INDEX, "title",
FTSearchParams.searchParams().sortBy("title", SortingOrder.ASC));
assertEquals(3, res.getTotalResults());
Document doc1 = res.getDocuments().get(0);
assertEquals("a title", doc1.get("title"));
doc1 = res.getDocuments().get(1);
assertEquals("b title", doc1.get("title"));
doc1 = res.getDocuments().get(2);
assertEquals("c title", doc1.get("title"));
}
@Test
public void testJsonWithAlias() {
assertOK(
jedis.ftCreate(INDEX, FTCreateParams.createParams().on(IndexDataType.JSON).prefix("king:"),
TextField.of("$.name").as("name"), NumericField.of("$.num").as("num")));
Map<String, Object> king1 = new HashMap<>();
king1.put("name", "henry");
king1.put("num", 42);
jedis.jsonSet("king:1", Path.ROOT_PATH, king1);
Map<String, Object> king2 = new HashMap<>();
king2.put("name", "james");
king2.put("num", 3.14);
jedis.jsonSet("king:2", Path.ROOT_PATH, king2);
SearchResult res = jedis.ftSearch(INDEX, "@name:henry");
assertEquals(1, res.getTotalResults());
assertEquals("king:1", res.getDocuments().get(0).getId());
res = jedis.ftSearch(INDEX, "@num:[0 10]");
assertEquals(1, res.getTotalResults());
assertEquals("king:2", res.getDocuments().get(0).getId());
res = jedis.ftSearch(INDEX, "@num:[42 42]", FTSearchParams.searchParams());
assertEquals(1, res.getTotalResults());
assertEquals("king:1", res.getDocuments().get(0).getId());
if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {
res = jedis.ftSearch(INDEX, "@num:[42]", FTSearchParams.searchParams().dialect(4));
assertEquals(1, res.getTotalResults());
assertEquals("king:1", res.getDocuments().get(0).getId());
}
}
@Test
public void noStem() {
assertOK(jedis.ftCreate(INDEX, new TextField("stemmed").weight(1.0),
new TextField("notStemmed").weight(1.0).noStem()));
Map<String, Object> doc = new HashMap<>();
doc.put("stemmed", "located");
doc.put("notStemmed", "located");
addDocument("doc", doc);
// Query
SearchResult res = jedis.ftSearch(INDEX, "@stemmed:location");
assertEquals(1, res.getTotalResults());
res = jedis.ftSearch(INDEX, "@notStemmed:location");
assertEquals(0, res.getTotalResults());
}
@Test
public void phoneticMatch() {
assertOK(jedis.ftCreate(INDEX, new TextField("noPhonetic").weight(1.0),
new TextField("withPhonetic").weight(1.0).phonetic("dm:en")));
Map<String, Object> doc = new HashMap<>();
doc.put("noPhonetic", "morfix");
doc.put("withPhonetic", "morfix");
addDocument("doc", doc);
// Query
SearchResult res = jedis.ftSearch(INDEX, "@withPhonetic:morphix=>{$phonetic:true}");
assertEquals(1, res.getTotalResults());
try {
jedis.ftSearch(INDEX, "@noPhonetic:morphix=>{$phonetic:true}");
fail();
} catch (JedisDataException e) {/* field does not support phonetics */
}
SearchResult res3 = jedis.ftSearch(INDEX, "@withPhonetic:morphix=>{$phonetic:false}");
assertEquals(0, res3.getTotalResults());
}
@Test
public void info() {
Collection<SchemaField> sc = new ArrayList<>();
sc.add(TextField.of("title").weight(5));
sc.add(TextField.of("plot").sortable());
sc.add(TagField.of("genre").separator(',').sortable());
sc.add(NumericField.of("release_year").sortable());
sc.add(NumericField.of("rating").sortable());
sc.add(NumericField.of("votes").sortable());
assertOK(jedis.ftCreate(INDEX, sc));
Map<String, Object> info = jedis.ftInfo(INDEX);
assertEquals(INDEX, info.get("index_name"));
assertEquals(6, ((List) info.get("attributes")).size());
if (protocol != RedisProtocol.RESP3) {
assertEquals("global_idle", ((List) info.get("cursor_stats")).get(0));
assertEquals(0L, ((List) info.get("cursor_stats")).get(1));
} else {
assertEquals(0L, ((Map) info.get("cursor_stats")).get("global_idle"));
}
}
@Test
public void noIndexAndSortBy() {
assertOK(jedis.ftCreate(INDEX, TextField.of("f1").sortable().noIndex(), TextField.of("f2")));
Map<String, Object> mm = new HashMap<>();
mm.put("f1", "MarkZZ");
mm.put("f2", "MarkZZ");
addDocument("doc1", mm);
mm.clear();
mm.put("f1", "MarkAA");
mm.put("f2", "MarkBB");
addDocument("doc2", mm);
SearchResult res = jedis.ftSearch(INDEX, "@f1:Mark*");
assertEquals(0, res.getTotalResults());
res = jedis.ftSearch(INDEX, "@f2:Mark*");
assertEquals(2, res.getTotalResults());
res = jedis.ftSearch(INDEX, "@f2:Mark*",
FTSearchParams.searchParams().sortBy("f1", SortingOrder.DESC));
assertEquals(2, res.getTotalResults());
assertEquals("doc1", res.getDocuments().get(0).getId());
res = jedis.ftSearch(INDEX, "@f2:Mark*",
FTSearchParams.searchParams().sortBy("f1", SortingOrder.ASC));
assertEquals("doc2", res.getDocuments().get(0).getId());
}
@Test
public void testHighlightSummarize() {
assertOK(jedis.ftCreate(INDEX, TextField.of("text").weight(1)));
Map<String, Object> doc = new HashMap<>();
doc.put("text", "Redis is often referred as a data structures server. What this means is that "
+ "Redis provides access to mutable data structures via a set of commands, which are sent "
+ "using a server-client model with TCP sockets and a simple protocol. So different "
+ "processes can query and modify the same data structures in a shared way");
// Add a document
addDocument("foo", doc);
SearchResult res = jedis.ftSearch(INDEX, "data",
FTSearchParams.searchParams().highlight().summarize());
assertEquals("is often referred as a <b>data</b> structures server. What this means is that "
+ "Redis provides... What this means is that Redis provides access to mutable <b>data</b> "
+ "structures via a set of commands, which are sent using a... So different processes can "
+ "query and modify the same <b>data</b> structures in a shared... ",
res.getDocuments().get(0).get("text"));
res = jedis.ftSearch(INDEX, "data", FTSearchParams.searchParams()
.highlight(FTSearchParams.highlightParams().tags("<u>", "</u>")).summarize());
assertEquals("is often referred as a <u>data</u> structures server. What this means is that "
+ "Redis provides... What this means is that Redis provides access to mutable <u>data</u> "
+ "structures via a set of commands, which are sent using a... So different processes can "
+ "query and modify the same <u>data</u> structures in a shared... ",
res.getDocuments().get(0).get("text"));
}
@Test
public void getTagField() {
assertOK(jedis.ftCreate(INDEX, TextField.of("title"), TagField.of("category")));
Map<String, Object> fields1 = new HashMap<>();
fields1.put("title", "hello world");
fields1.put("category", "red");
addDocument("foo", fields1);
Map<String, Object> fields2 = new HashMap<>();
fields2.put("title", "hello world");
fields2.put("category", "blue");
addDocument("bar", fields2);
Map<String, Object> fields3 = new HashMap<>();
fields3.put("title", "hello world");
fields3.put("category", "green,yellow");
addDocument("baz", fields3);
Map<String, Object> fields4 = new HashMap<>();
fields4.put("title", "hello world");
fields4.put("category", "orange;purple");
addDocument("qux", fields4);
assertEquals(1, jedis.ftSearch(INDEX, "@category:{red}").getTotalResults());
assertEquals(1, jedis.ftSearch(INDEX, "@category:{blue}").getTotalResults());
assertEquals(1, jedis.ftSearch(INDEX, "hello @category:{red}").getTotalResults());
assertEquals(1, jedis.ftSearch(INDEX, "hello @category:{blue}").getTotalResults());
assertEquals(1, jedis.ftSearch(INDEX, "@category:{yellow}").getTotalResults());
assertEquals(0, jedis.ftSearch(INDEX, "@category:{purple}").getTotalResults());
assertEquals(1, jedis.ftSearch(INDEX, "@category:{orange\\;purple}").getTotalResults());
assertEquals(4, jedis.ftSearch(INDEX, "hello").getTotalResults());
assertEquals(new HashSet<>(Arrays.asList("red", "blue", "green", "yellow", "orange;purple")),
jedis.ftTagVals(INDEX, "category"));
}
@Test
public void testGetTagFieldWithNonDefaultSeparator() {
assertOK(jedis.ftCreate(INDEX, TextField.of("title"), TagField.of("category").separator(';')));
Map<String, Object> fields1 = new HashMap<>();
fields1.put("title", "hello world");
fields1.put("category", "red");
addDocument("foo", fields1);
Map<String, Object> fields2 = new HashMap<>();
fields2.put("title", "hello world");
fields2.put("category", "blue");
addDocument("bar", fields2);
Map<String, Object> fields3 = new HashMap<>();
fields3.put("title", "hello world");
fields3.put("category", "green;yellow");
addDocument("baz", fields3);
Map<String, Object> fields4 = new HashMap<>();
fields4.put("title", "hello world");
fields4.put("category", "orange,purple");
addDocument("qux", fields4);
assertEquals(1, jedis.ftSearch(INDEX, "@category:{red}").getTotalResults());
assertEquals(1, jedis.ftSearch(INDEX, "@category:{blue}").getTotalResults());
assertEquals(1, jedis.ftSearch(INDEX, "hello @category:{red}").getTotalResults());
assertEquals(1, jedis.ftSearch(INDEX, "hello @category:{blue}").getTotalResults());
assertEquals(1, jedis.ftSearch(INDEX, "hello @category:{yellow}").getTotalResults());
assertEquals(0, jedis.ftSearch(INDEX, "@category:{purple}").getTotalResults());
assertEquals(1, jedis.ftSearch(INDEX, "@category:{orange\\,purple}").getTotalResults());
assertEquals(4, jedis.ftSearch(INDEX, "hello").getTotalResults());
assertEquals(new HashSet<>(Arrays.asList("red", "blue", "green", "yellow", "orange,purple")),
jedis.ftTagVals(INDEX, "category"));
}
@Test
public void caseSensitiveTagField() {
assertOK(jedis.ftCreate(INDEX, TextField.of("title"), TagField.of("category").caseSensitive()));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
fields.put("category", "RedX");
addDocument("foo", fields);
assertEquals(0, jedis.ftSearch(INDEX, "@category:{redx}").getTotalResults());
assertEquals(0, jedis.ftSearch(INDEX, "@category:{redX}").getTotalResults());
assertEquals(0, jedis.ftSearch(INDEX, "@category:{Redx}").getTotalResults());
assertEquals(1, jedis.ftSearch(INDEX, "@category:{RedX}").getTotalResults());
assertEquals(1, jedis.ftSearch(INDEX, "hello").getTotalResults());
}
@Test
public void tagFieldParams() {
assertOK(jedis.ftCreate("testindex", TextField.of("title"), TagField.of("category").as("cat")
.separator(',').caseSensitive().withSuffixTrie().sortable()));
assertOK(jedis.ftCreate("testunfindex", TextField.of("title"), TagField.of("category").as("cat")
.separator(',').caseSensitive().withSuffixTrie().sortableUNF()));
if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {
assertOK(jedis.ftCreate("testindex-missing", TextField.of("title"),
TagField.of("category").as("cat").indexMissing().indexEmpty().separator(',').caseSensitive()
.withSuffixTrie().sortable()));
assertOK(jedis.ftCreate("testunfindex-missing", TextField.of("title"),
TagField.of("category").as("cat").indexMissing().indexEmpty().separator(',').caseSensitive()
.withSuffixTrie().sortableUNF()));
}
assertOK(jedis.ftCreate("testnoindex", TextField.of("title"),
TagField.of("category").as("cat").sortable().noIndex()));
assertOK(jedis.ftCreate("testunfnoindex", TextField.of("title"),
TagField.of("category").as("cat").sortableUNF().noIndex()));
}
@Test
public void returnFields() {
assertOK(jedis.ftCreate(INDEX, TextField.of("field1"), TextField.of("field2")));
Map<String, Object> doc = new HashMap<>();
doc.put("field1", "value1");
doc.put("field2", "value2");
addDocument("doc", doc);
// Query
SearchResult res = jedis.ftSearch(INDEX, "*",
FTSearchParams.searchParams().returnFields("field1"));
assertEquals(1, res.getTotalResults());
Document ret = res.getDocuments().get(0);
assertEquals("value1", ret.get("field1"));
assertNull(ret.get("field2"));
res = jedis.ftSearch(INDEX, "*", FTSearchParams.searchParams().returnField("field1", true));
assertEquals("value1", res.getDocuments().get(0).get("field1"));
res = jedis.ftSearch(INDEX, "*", FTSearchParams.searchParams().returnField("field1", false));
assertArrayEquals("value1".getBytes(), (byte[]) res.getDocuments().get(0).get("field1"));
}
@Test
public void returnFieldsNames() {
assertOK(jedis.ftCreate(INDEX, TextField.of("a"), TextField.of("b"), TextField.of("c")));
Map<String, Object> map = new HashMap<>();
map.put("a", "value1");
map.put("b", "value2");
map.put("c", "value3");
addDocument("doc", map);
// Query
SearchResult res = jedis.ftSearch(INDEX, "*",
FTSearchParams.searchParams().returnFields(FieldName.of("a"), FieldName.of("b").as("d")));
assertEquals(1, res.getTotalResults());
Document doc = res.getDocuments().get(0);
assertEquals("value1", doc.get("a"));
assertNull(doc.get("b"));
assertEquals("value2", doc.get("d"));
assertNull(doc.get("c"));
res = jedis.ftSearch(INDEX, "*", FTSearchParams.searchParams().returnField(FieldName.of("a"))
.returnField(FieldName.of("b").as("d")));
assertEquals(1, res.getTotalResults());
assertEquals("value1", res.getDocuments().get(0).get("a"));
assertEquals("value2", res.getDocuments().get(0).get("d"));
res = jedis.ftSearch(INDEX, "*", FTSearchParams.searchParams()
.returnField(FieldName.of("a"), true).returnField(FieldName.of("b").as("d"), true));
assertEquals(1, res.getTotalResults());
assertEquals("value1", res.getDocuments().get(0).get("a"));
assertEquals("value2", res.getDocuments().get(0).get("d"));
res = jedis.ftSearch(INDEX, "*", FTSearchParams.searchParams()
.returnField(FieldName.of("a"), false).returnField(FieldName.of("b").as("d"), false));
assertEquals(1, res.getTotalResults());
assertArrayEquals("value1".getBytes(), (byte[]) res.getDocuments().get(0).get("a"));
assertArrayEquals("value2".getBytes(), (byte[]) res.getDocuments().get(0).get("d"));
}
@Test
public void inKeys() {
assertOK(jedis.ftCreate(INDEX, TextField.of("field1"), TextField.of("field2")));
Map<String, Object> doc = new HashMap<>();
doc.put("field1", "value");
doc.put("field2", "not");
// Store it
addDocument("doc1", doc);
addDocument("doc2", doc);
// Query
SearchResult res = jedis.ftSearch(INDEX, "value", FTSearchParams.searchParams().inKeys("doc1"));
assertEquals(1, res.getTotalResults());
assertEquals("doc1", res.getDocuments().get(0).getId());
assertEquals("value", res.getDocuments().get(0).get("field1"));
assertNull(res.getDocuments().get(0).get("value"));
}
@Test
public void slop() {
assertOK(jedis.ftCreate(INDEX, TextField.of("field1"), TextField.of("field2")));
Map<String, Object> doc = new HashMap<>();
doc.put("field1", "ok hi jedis");
addDocument("doc1", doc);
SearchResult res = jedis.ftSearch(INDEX, "ok jedis", FTSearchParams.searchParams().slop(0));
assertEquals(0, res.getTotalResults());
res = jedis.ftSearch(INDEX, "ok jedis", FTSearchParams.searchParams().slop(1));
assertEquals(1, res.getTotalResults());
assertEquals("doc1", res.getDocuments().get(0).getId());
assertEquals("ok hi jedis", res.getDocuments().get(0).get("field1"));
}
@Test
public void timeout() {
assertOK(jedis.ftCreate(INDEX, TextField.of("field1"), TextField.of("field2")));
Map<String, String> map = new HashMap<>();
map.put("field1", "value");
map.put("field2", "not");
jedis.hset("doc1", map);
SearchResult res = jedis.ftSearch(INDEX, "value", FTSearchParams.searchParams().timeout(1000));
assertEquals(1, res.getTotalResults());
assertEquals("doc1", res.getDocuments().get(0).getId());
assertEquals("value", res.getDocuments().get(0).get("field1"));
assertEquals("not", res.getDocuments().get(0).get("field2"));
}
@Test
public void inOrder() {
assertOK(jedis.ftCreate(INDEX, TextField.of("field1"), TextField.of("field2")));
Map<String, String> map = new HashMap<>();
map.put("field1", "value");
map.put("field2", "not");
jedis.hset("doc2", map);
jedis.hset("doc1", map);
SearchResult res = jedis.ftSearch(INDEX, "value", FTSearchParams.searchParams().inOrder());
assertEquals(2, res.getTotalResults());
assertEquals("doc2", res.getDocuments().get(0).getId());
assertEquals("value", res.getDocuments().get(0).get("field1"));
assertEquals("not", res.getDocuments().get(0).get("field2"));
}
@Test
public void testHNSWVectorSimilarity() {
Map<String, Object> attr = new HashMap<>();
attr.put("TYPE", "FLOAT32");
attr.put("DIM", 2);
attr.put("DISTANCE_METRIC", "L2");
assertOK(jedis.ftCreate(INDEX, VectorField.builder().fieldName("v")
.algorithm(VectorAlgorithm.HNSW).attributes(attr).build()));
jedis.hset("a", "v", "aaaaaaaa");
jedis.hset("b", "v", "aaaabaaa");
jedis.hset("c", "v", "aaaaabaa");
FTSearchParams searchParams = FTSearchParams.searchParams().addParam("vec", "aaaaaaaa")
.sortBy("__v_score", SortingOrder.ASC).returnFields("__v_score").dialect(2);
Document doc1 = jedis.ftSearch(INDEX, "*=>[KNN 2 @v $vec]", searchParams).getDocuments().get(0);
assertEquals("a", doc1.getId());
assertEquals("0", doc1.get("__v_score"));
}
@Test
public void testFlatVectorSimilarity() {
assertOK(jedis.ftCreate(INDEX,
VectorField.builder().fieldName("v").algorithm(VectorAlgorithm.FLAT)
.addAttribute("TYPE", "FLOAT32").addAttribute("DIM", 2)
.addAttribute("DISTANCE_METRIC", "L2").build()));
jedis.hset("a", "v", "aaaaaaaa");
jedis.hset("b", "v", "aaaabaaa");
jedis.hset("c", "v", "aaaaabaa");
FTSearchParams searchParams = FTSearchParams.searchParams().addParam("vec", "aaaaaaaa")
.sortBy("__v_score", SortingOrder.ASC).returnFields("__v_score").dialect(2);
Document doc1 = jedis.ftSearch(INDEX, "*=>[KNN 2 @v $vec]", searchParams).getDocuments().get(0);
assertEquals("a", doc1.getId());
assertEquals("0", doc1.get("__v_score"));
}
@Test
public void testFlatVectorSimilarityInt8() {
assumeTrue(RedisConditions.of(jedis).moduleVersionIsGreaterThanOrEqual(SEARCH_MOD_VER_80M3),
"INT8");
assertOK(jedis.ftCreate(INDEX,
VectorField.builder().fieldName("v").algorithm(VectorAlgorithm.FLAT)
.addAttribute("TYPE", "INT8").addAttribute("DIM", 2).addAttribute("DISTANCE_METRIC", "L2")
.build()));
byte[] a = { 127, 1 };
byte[] b = { 127, 10 };
byte[] c = { 127, 100 };
jedis.hset("a".getBytes(), "v".getBytes(), a);
jedis.hset("b".getBytes(), "v".getBytes(), b);
jedis.hset("c".getBytes(), "v".getBytes(), c);
FTSearchParams searchParams = FTSearchParams.searchParams().addParam("vec", a)
.sortBy("__v_score", SortingOrder.ASC).returnFields("__v_score");
Document doc1 = jedis.ftSearch(INDEX, "*=>[KNN 2 @v $vec]", searchParams).getDocuments().get(0);
assertEquals("a", doc1.getId());
assertEquals("0", doc1.get("__v_score"));
}
@Test
@SinceRedisVersion(value = "7.4.0", message = "no optional params before 7.4.0")
public void vectorFieldParams() {
Map<String, Object> attr = new HashMap<>();
attr.put("TYPE", "FLOAT32");
attr.put("DIM", 2);
attr.put("DISTANCE_METRIC", "L2");
assertOK(jedis.ftCreate("testindex-missing",
new VectorField("vector", VectorAlgorithm.HNSW, attr).as("vec").indexMissing()));
}
@Test
@SinceRedisVersion(value = "7.4.0", message = "FLOAT16")
public void float16StorageType() {
assertOK(jedis.ftCreate(INDEX,
VectorField.builder().fieldName("v").algorithm(VectorAlgorithm.HNSW)
.addAttribute("TYPE", "FLOAT16").addAttribute("DIM", 4)
.addAttribute("DISTANCE_METRIC", "L2").build()));
}
@Test
@SinceRedisVersion(value = "7.4.0", message = "BFLOAT16")
public void bfloat16StorageType() {
assertOK(jedis.ftCreate(INDEX,
VectorField.builder().fieldName("v").algorithm(VectorAlgorithm.HNSW)
.addAttribute("TYPE", "BFLOAT16").addAttribute("DIM", 4)
.addAttribute("DISTANCE_METRIC", "L2").build()));
}
@Test
public void int8StorageType() {
assumeTrue(RedisConditions.of(jedis).moduleVersionIsGreaterThanOrEqual(SEARCH_MOD_VER_80M3),
"INT8");
assertOK(jedis.ftCreate(INDEX,
VectorField.builder().fieldName("v").algorithm(VectorAlgorithm.HNSW)
.addAttribute("TYPE", "INT8").addAttribute("DIM", 4).addAttribute("DISTANCE_METRIC", "L2")
.build()));
}
@Test
public void uint8StorageType() {
assumeTrue(RedisConditions.of(jedis).moduleVersionIsGreaterThanOrEqual(SEARCH_MOD_VER_80M3),
"UINT8");
assertOK(jedis.ftCreate(INDEX,
VectorField.builder().fieldName("v").algorithm(VectorAlgorithm.HNSW)
.addAttribute("TYPE", "UINT8").addAttribute("DIM", 4)
.addAttribute("DISTANCE_METRIC", "L2").build()));
}
@Test
@SinceRedisVersion("8.1.240")
public void testSvsVamanaVectorSimilarity() {
Map<String, Object> attr = new HashMap<>();
attr.put("TYPE", "FLOAT32");
attr.put("DIM", 2);
attr.put("DISTANCE_METRIC", "L2");
assertOK(jedis.ftCreate(INDEX, VectorField.builder().fieldName("v")
.algorithm(VectorAlgorithm.SVS_VAMANA).attributes(attr).build()));
// Create proper float vectors
float[] vectorA = { 1.0f, 2.0f };
float[] vectorB = { 1.1f, 2.1f };
float[] vectorC = { 2.0f, 3.0f };
// Convert to byte arrays using RediSearchUtil
byte[] bytesA = RediSearchUtil.toByteArray(vectorA);
byte[] bytesB = RediSearchUtil.toByteArray(vectorB);
byte[] bytesC = RediSearchUtil.toByteArray(vectorC);
jedis.hset("a".getBytes(), "v".getBytes(), bytesA);
jedis.hset("b".getBytes(), "v".getBytes(), bytesB);
jedis.hset("c".getBytes(), "v".getBytes(), bytesC);
FTSearchParams searchParams = FTSearchParams.searchParams().addParam("vec", bytesA)
.sortBy("__v_score", SortingOrder.ASC).returnFields("__v_score").dialect(2);
Document doc1 = jedis.ftSearch(INDEX, "*=>[KNN 2 @v $vec]", searchParams).getDocuments().get(0);
assertEquals("a", doc1.getId());
assertEquals("0", doc1.get("__v_score"));
}
@Test
@SinceRedisVersion("8.1.240")
public void testSvsVamanaVectorWithAdvancedParameters() {
assertOK(jedis.ftCreate(INDEX,
VectorField.builder().fieldName("v").algorithm(VectorAlgorithm.SVS_VAMANA)
.addAttribute("TYPE", "FLOAT32").addAttribute("DIM", 4)
.addAttribute("DISTANCE_METRIC", "L2").addAttribute("CONSTRUCTION_WINDOW_SIZE", 200)
.addAttribute("GRAPH_MAX_DEGREE", 64).addAttribute("SEARCH_WINDOW_SIZE", 100)
.addAttribute("EPSILON", 0.01).build()));
}
@Test
public void searchProfile() {
assertOK(jedis.ftCreate(INDEX, TextField.of("t1"), TextField.of("t2")));
Map<String, String> hash = new HashMap<>();
hash.put("t1", "foo");
hash.put("t2", "bar");
jedis.hset("doc1", hash);
Map.Entry<SearchResult, ProfilingInfo> reply = jedis.ftProfileSearch(INDEX,
FTProfileParams.profileParams(), "foo", FTSearchParams.searchParams());
SearchResult result = reply.getKey();
assertEquals(1, result.getTotalResults());
assertEquals(Collections.singletonList("doc1"),
result.getDocuments().stream().map(Document::getId).collect(Collectors.toList()));
// profile
Object profileObject = reply.getValue().getProfilingInfo();
if (protocol != RedisProtocol.RESP3) {
assertThat(profileObject, Matchers.isA(List.class));
if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {
assertThat((List<Object>) profileObject, Matchers.hasItems("Shards", "Coordinator"));
}
} else {
assertThat(profileObject, Matchers.isA(Map.class));
if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {
assertThat(((Map<String, Object>) profileObject).keySet(),
Matchers.hasItems("Shards", "Coordinator"));
}
}
}
@Test
public void vectorSearchProfile() {
assertOK(jedis.ftCreate(INDEX,
VectorField.builder().fieldName("v").algorithm(VectorAlgorithm.FLAT)
.addAttribute("TYPE", "FLOAT32").addAttribute("DIM", 2)
.addAttribute("DISTANCE_METRIC", "L2").build(),
TextField.of("t")));
jedis.hset("1", toStringMap("v", "bababaca", "t", "hello"));
jedis.hset("2", toStringMap("v", "babababa", "t", "hello"));
jedis.hset("3", toStringMap("v", "aabbaabb", "t", "hello"));
jedis.hset("4", toStringMap("v", "bbaabbaa", "t", "hello world"));
jedis.hset("5", toStringMap("v", "aaaabbbb", "t", "hello world"));
FTSearchParams searchParams = FTSearchParams.searchParams().addParam("vec", "aaaaaaaa")
.sortBy("__v_score", SortingOrder.ASC).noContent().dialect(2);
Map.Entry<SearchResult, ProfilingInfo> reply = jedis.ftProfileSearch(INDEX,
FTProfileParams.profileParams(), "*=>[KNN 3 @v $vec]", searchParams);
assertEquals(3, reply.getKey().getTotalResults());
assertEquals(Arrays.asList(4, 2, 1).toString(), reply.getKey().getDocuments().stream()
.map(Document::getId).collect(Collectors.toList()).toString());
// profile
Object profileObject = reply.getValue().getProfilingInfo();
if (protocol != RedisProtocol.RESP3) {
assertThat(profileObject, Matchers.isA(List.class));
if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {
assertThat((List<Object>) profileObject, Matchers.hasItems("Shards", "Coordinator"));
}
} else {
assertThat(profileObject, Matchers.isA(Map.class));
if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {
assertThat(((Map<String, Object>) profileObject).keySet(),
Matchers.hasItems("Shards", "Coordinator"));
}
}
}
@Test
public void limitedSearchProfile() {
assertOK(jedis.ftCreate(INDEX, TextField.of("t")));
jedis.hset("1", "t", "hello");
jedis.hset("2", "t", "hell");
jedis.hset("3", "t", "help");
jedis.hset("4", "t", "helowa");
Map.Entry<SearchResult, ProfilingInfo> reply = jedis.ftProfileSearch(INDEX,
FTProfileParams.profileParams().limited(), "%hell% hel*",
FTSearchParams.searchParams().noContent());
// profile
Object profileObject = reply.getValue().getProfilingInfo();
if (protocol != RedisProtocol.RESP3) {
assertThat(profileObject, Matchers.isA(List.class));
if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {
assertThat((List<Object>) profileObject, Matchers.hasItems("Shards", "Coordinator"));
}
} else {
assertThat(profileObject, Matchers.isA(Map.class));
if (RedisVersionUtil.getRedisVersion(jedis).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {
assertThat(((Map<String, Object>) profileObject).keySet(),
Matchers.hasItems("Shards", "Coordinator"));
}
}
}
@Test
public void list() {
assertEquals(Collections.emptySet(), jedis.ftList());
final int count = 20;
Set<String> names = new HashSet<>();
for (int i = 0; i < count; i++) {
final String name = "idx" + i;
assertOK(jedis.ftCreate(name, TextField.of("t" + i)));
names.add(name);
}
assertEquals(names, jedis.ftList());
}
@Test
public void broadcast() {
String reply = jedis.ftCreate(INDEX, TextField.of("t"));
assertOK(reply);
}
@Test
public void searchIteration() {
assertOK(jedis.ftCreate(INDEX, FTCreateParams.createParams(), TextField.of("first"),
TextField.of("last"), NumericField.of("age")));
jedis.hset("profesor:5555", toStringMap("first", "Albert", "last", "Blue", "age", "55"));
jedis.hset("student:1111", toStringMap("first", "Joe", "last", "Dod", "age", "18"));
jedis.hset("pupil:2222", toStringMap("first", "Jen", "last", "Rod", "age", "14"));
jedis.hset("student:3333", toStringMap("first", "El", "last", "Mark", "age", "17"));
jedis.hset("pupil:4444", toStringMap("first", "Pat", "last", "Shu", "age", "21"));
jedis.hset("student:5555", toStringMap("first", "Joen", "last", "Ko", "age", "20"));
jedis.hset("teacher:6666", toStringMap("first", "Pat", "last", "Rod", "age", "20"));
FtSearchIteration search = jedis.ftSearchIteration(3, INDEX, "*",
FTSearchParams.searchParams());
int total = 0;
while (!search.isIterationCompleted()) {
SearchResult result = search.nextBatch();
int count = result.getDocuments().size();
assertThat(count, Matchers.lessThanOrEqualTo(3));
total += count;
}
assertEquals(7, total);
}
@Test
public void searchIterationCollect() {
assertOK(jedis.ftCreate(INDEX, FTCreateParams.createParams(), TextField.of("first"),
TextField.of("last"), NumericField.of("age")));
jedis.hset("profesor:5555", toStringMap("first", "Albert", "last", "Blue", "age", "55"));
jedis.hset("student:1111", toStringMap("first", "Joe", "last", "Dod", "age", "18"));
jedis.hset("pupil:2222", toStringMap("first", "Jen", "last", "Rod", "age", "14"));
jedis.hset("student:3333", toStringMap("first", "El", "last", "Mark", "age", "17"));
jedis.hset("pupil:4444", toStringMap("first", "Pat", "last", "Shu", "age", "21"));
jedis.hset("student:5555", toStringMap("first", "Joen", "last", "Ko", "age", "20"));
jedis.hset("teacher:6666", toStringMap("first", "Pat", "last", "Rod", "age", "20"));
ArrayList<Document> collect = new ArrayList<>();
jedis.ftSearchIteration(3, INDEX, "*", FTSearchParams.searchParams()).collect(collect);
assertEquals(7, collect.size());
assertEquals(
Arrays.asList("profesor:5555", "student:1111", "pupil:2222", "student:3333", "pupil:4444",
"student:5555", "teacher:6666").stream().collect(Collectors.toSet()),
collect.stream().map(Document::getId).collect(Collectors.toSet()));
}
@Test
public void escapeUtil() {
assertOK(jedis.ftCreate(INDEX, TextField.of("txt")));
jedis.hset("doc1", "txt", RediSearchUtil.escape("hello-world"));
assertNotEquals("hello-world", jedis.hget("doc1", "txt"));
assertEquals("hello-world", RediSearchUtil.unescape(jedis.hget("doc1", "txt")));
SearchResult resultNoEscape = jedis.ftSearch(INDEX, "hello-world");
assertEquals(0, resultNoEscape.getTotalResults());
SearchResult resultEscaped = jedis.ftSearch(INDEX, RediSearchUtil.escapeQuery("hello-world"));
assertEquals(1, resultEscaped.getTotalResults());
}
@Test
public void escapeMapUtil() {
jedis.hset("doc2",
RediSearchUtil.toStringMap(Collections.singletonMap("txt", "hello-world"), true));
assertNotEquals("hello-world", jedis.hget("doc2", "txt"));
assertEquals("hello-world", RediSearchUtil.unescape(jedis.hget("doc2", "txt")));
}
@Test
public void hsetObject() {
float[] floats = new float[] { 0.2f };
assertEquals(1L, jedis.hsetObject("obj", "floats", floats));
assertArrayEquals(RediSearchUtil.toByteArray(floats),
jedis.hget("obj".getBytes(), "floats".getBytes()));
GeoCoordinate geo = new GeoCoordinate(-0.441, 51.458);
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
fields.put("loc", geo);
assertEquals(2L, jedis.hsetObject("obj", fields));
Map<String, String> stringMap = jedis.hgetAll("obj");
assertEquals(3, stringMap.size());
assertEquals("hello world", stringMap.get("title"));
assertEquals(geo.getLongitude() + "," + geo.getLatitude(), stringMap.get("loc"));
}
}