SearchWithParamsTest.java
package redis.clients.jedis.modules.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.params.ParameterizedClass;
import org.junit.jupiter.params.provider.MethodSource;
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.GeoCoordinate;
import redis.clients.jedis.RedisProtocol;
import redis.clients.jedis.args.GeoUnit;
import redis.clients.jedis.args.SortingOrder;
import redis.clients.jedis.exceptions.JedisDataException;
import redis.clients.jedis.json.Path;
import redis.clients.jedis.search.*;
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.modules.RedisModuleCommandsTestBase;
import redis.clients.jedis.util.RedisConditions;
import redis.clients.jedis.util.RedisVersionUtil;
@ParameterizedClass
@MethodSource("redis.clients.jedis.commands.CommandsTestsParameters#respVersions")
public class SearchWithParamsTest extends RedisModuleCommandsTestBase {
private static final String index = "testindex";
@BeforeAll
public static void prepare() {
RedisModuleCommandsTestBase.prepare();
}
@AfterEach
public void cleanUp() {
if (client.ftList().contains(index)) {
client.ftDropIndex(index);
}
}
public SearchWithParamsTest(RedisProtocol protocol) {
super(protocol);
}
private void addDocument(String key, Map<String, Object> map) {
client.hset(key, RediSearchUtil.toStringMap(map));
}
private 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;
}
private static Map<String, String> toMap(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(client.ftCreate(index,
FTCreateParams.createParams()
.filter("@age>16")
.prefix("student:", "pupil:"),
TextField.of("first"), TextField.of("last"), NumericField.of("age")));
client.hset("profesor:5555", toMap("first", "Albert", "last", "Blue", "age", "55"));
client.hset("student:1111", toMap("first", "Joe", "last", "Dod", "age", "18"));
client.hset("pupil:2222", toMap("first", "Jen", "last", "Rod", "age", "14"));
client.hset("student:3333", toMap("first", "El", "last", "Mark", "age", "17"));
client.hset("pupil:4444", toMap("first", "Pat", "last", "Shu", "age", "21"));
client.hset("student:5555", toMap("first", "Joen", "last", "Ko", "age", "20"));
client.hset("teacher:6666", toMap("first", "Pat", "last", "Rod", "age", "20"));
SearchResult noFilters = client.ftSearch(index);
assertEquals(4, noFilters.getTotalResults());
SearchResult res1 = client.ftSearch(index, "@first:Jo*");
assertEquals(2, res1.getTotalResults());
SearchResult res2 = client.ftSearch(index, "@first:Pat");
assertEquals(1, res2.getTotalResults());
SearchResult res3 = client.ftSearch(index, "@last:Rod");
assertEquals(0, res3.getTotalResults());
}
@Test
public void createNoParams() {
assertOK(client.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 = client.ftSearch(index);
assertEquals(4, noFilters.getTotalResults());
SearchResult res1 = client.ftSearch(index, "@first:Jo*");
assertEquals(2, res1.getTotalResults());
SearchResult res2 = client.ftSearch(index, "@first:Pat");
assertEquals(1, res2.getTotalResults());
SearchResult res3 = client.ftSearch(index, "@last:Rod");
assertEquals(0, res3.getTotalResults());
}
@Test
public void createWithFieldNames() {
assertOK(client.ftCreate(index,
FTCreateParams.createParams()
.addPrefix("student:").addPrefix("pupil:"),
TextField.of("first").as("given"),
TextField.of(FieldName.of("last").as("family"))));
client.hset("profesor:5555", toMap("first", "Albert", "last", "Blue", "age", "55"));
client.hset("student:1111", toMap("first", "Joe", "last", "Dod", "age", "18"));
client.hset("pupil:2222", toMap("first", "Jen", "last", "Rod", "age", "14"));
client.hset("student:3333", toMap("first", "El", "last", "Mark", "age", "17"));
client.hset("pupil:4444", toMap("first", "Pat", "last", "Shu", "age", "21"));
client.hset("student:5555", toMap("first", "Joen", "last", "Ko", "age", "20"));
client.hset("teacher:6666", toMap("first", "Pat", "last", "Rod", "age", "20"));
SearchResult noFilters = client.ftSearch(index);
assertEquals(5, noFilters.getTotalResults());
SearchResult asGiven = client.ftSearch(index, "@given:Jo*");
assertEquals(2, asGiven.getTotalResults());
SearchResult nonLast = client.ftSearch(index, "@last:Rod");
assertEquals(0, nonLast.getTotalResults());
SearchResult asFamily = client.ftSearch(index, "@family:Rod");
assertEquals(1, asFamily.getTotalResults());
}
@Test
public void alterAdd() {
assertOK(client.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 = client.ftSearch(index, "hello world");
assertEquals(100, res.getTotalResults());
assertOK(client.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 = client.ftSearch(index, "@tags:{tagA}");
assertEquals(100, res2.getTotalResults());
}
@Test
public void search() {
assertOK(client.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 = client.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);
}
client.del("doc0");
result = client.ftSearch(index, "hello world");
assertEquals(99, result.getTotalResults());
assertEquals("OK", client.ftDropIndex(index));
try {
client.ftSearch(index, "hello world");
fail();
} catch (JedisDataException e) {
}
}
@Test
public void textFieldParams() {
assertOK(client.ftCreate("testindex", TextField.of("title")
.weight(2.5).noStem().phonetic("dm:en").withSuffixTrie().sortable()));
assertOK(client.ftCreate("testunfindex", TextField.of("title")
.weight(2.5).noStem().phonetic("dm:en").withSuffixTrie().sortableUNF()));
if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {
assertOK(client.ftCreate("testindex-missing",
TextField.of("title").indexMissing().indexEmpty().weight(2.5).noStem().phonetic("dm:en")
.withSuffixTrie().sortable()));
assertOK(client.ftCreate("testunfindex-missing",
TextField.of("title").indexMissing().indexEmpty().weight(2.5).noStem().phonetic("dm:en")
.withSuffixTrie().sortableUNF()));
}
assertOK(client.ftCreate("testnoindex", TextField.of("title").sortable().noIndex()));
assertOK(client.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(client.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");
client.hset("regular-doc", regular);
Map<String, String> empty = new HashMap<>();
empty.put("title", "hello world");
empty.put("body", "");
client.hset("empty-doc", empty);
Map<String, String> missing = new HashMap<>();
missing.put("title", "hello world");
client.hset("missing-doc", missing);
SearchResult result = client.ftSearch(index, "", FTSearchParams.searchParams().dialect(2));
assertEquals(0, result.getTotalResults());
assertEquals(0, result.getDocuments().size());
result = client.ftSearch(index, "*", FTSearchParams.searchParams().dialect(2));
assertEquals(3, result.getTotalResults());
assertEquals(3, result.getDocuments().size());
result = client.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 = client.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 numericFilter() {
assertOK(client.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 = client.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 = client.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 = client.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 = client.ftSearch(index, "hello world",
FTSearchParams.searchParams()
.filter("price", 20, Double.POSITIVE_INFINITY));
assertEquals(80, res.getTotalResults());
assertEquals(10, res.getDocuments().size());
res = client.ftSearch(index, "hello world",
FTSearchParams.searchParams()
.filter("price", Double.NEGATIVE_INFINITY, 10));
assertEquals(11, res.getTotalResults());
assertEquals(10, res.getDocuments().size());
}
@Test
public void numericFieldParams() {
assertOK(client.ftCreate("testindex", TextField.of("title"),
NumericField.of("price").as("px").sortable()));
if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {
assertOK(client.ftCreate("testindex-missing", TextField.of("title"),
NumericField.of("price").as("px").indexMissing().sortable()));
}
assertOK(client.ftCreate("testnoindex", TextField.of("title"),
NumericField.of("price").as("px").sortable().noIndex()));
}
@Test
public void stopwords() {
assertOK(client.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 = client.ftSearch(index, "hello world");
assertEquals(1, res.getTotalResults());
res = client.ftSearch(index, "foo bar");
assertEquals(0, res.getTotalResults());
}
@Test
public void noStopwords() {
assertOK(client.ftCreate(index,
FTCreateParams.createParams().noStopwords(),
TextField.of("title")));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world foo bar");
fields.put("title", "hello world foo bar to be or not to be");
addDocument("doc1", fields);
assertEquals(1, client.ftSearch(index, "hello world").getTotalResults());
assertEquals(1, client.ftSearch(index, "foo bar").getTotalResults());
assertEquals(1, client.ftSearch(index, "to be or not to be").getTotalResults());
}
@Test
public void geoFilter() {
assertOK(client.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 = client.ftSearch(index, "hello world",
FTSearchParams.searchParams().
geoFilter("loc", -0.44, 51.45, 10, GeoUnit.KM));
assertEquals(1, res.getTotalResults());
res = client.ftSearch(index, "hello world",
FTSearchParams.searchParams().
geoFilter("loc", -0.44, 51.45, 100, GeoUnit.KM));
assertEquals(2, res.getTotalResults());
}
@Test
public void geoFilterAndGeoCoordinateObject() {
assertOK(client.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 = client.ftSearch(index, "hello world",
FTSearchParams.searchParams()
.geoFilter(new FTSearchParams.GeoFilter("loc", -0.44, 51.45, 10, GeoUnit.KM)));
assertEquals(1, res.getTotalResults());
res = client.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(client.ftCreate("testindex", TextField.of("title"),
GeoField.of("location").as("loc").sortable()));
if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {
assertOK(client.ftCreate("testindex-missing", TextField.of("title"),
GeoField.of("location").as("loc").indexMissing().sortable()));
}
assertOK(client.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(client.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)});
client.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)});
client.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 = client.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 = client.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));
client.hset("point", "geom", point.toString());
result = client.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(client.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)});
client.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)});
client.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 = client.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 = client.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(client).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 = client.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 = client.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));
client.hsetObject("point", "geom", point);
result = client.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(client).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {
assertOK(client.ftCreate("testindex-missing",
GeoShapeField.of("geometry", CoordinateSystem.SPHERICAL).as("geom").indexMissing()));
}
assertOK(client.ftCreate("testnoindex",
GeoShapeField.of("geometry", CoordinateSystem.SPHERICAL).as("geom").noIndex()));
}
@Test
public void testQueryFlags() {
assertOK(client.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 = client.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 = client.ftSearch(index, "hello",
// FTSearchParams.searchParams().withScores().explainScore());
// 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 = client.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 = client.ftSearch(index, "hello worlds");
assertEquals(100, res.getTotalResults());
res = client.ftSearch(index, "hello worlds", FTSearchParams.searchParams().verbatim());
assertEquals(50, res.getTotalResults());
res = client.ftSearch(index, "hello a world", FTSearchParams.searchParams().verbatim());
assertEquals(50, res.getTotalResults());
res = client.ftSearch(index, "hello a worlds", FTSearchParams.searchParams().verbatim());
assertEquals(50, res.getTotalResults());
res = client.ftSearch(index, "hello a world", FTSearchParams.searchParams().verbatim().noStopwords());
assertEquals(0, res.getTotalResults());
}
@Test
public void testQueryParams() {
assertOK(client.ftCreate(index, NumericField.of("numval")));
client.hset("1", "numval", "1");
client.hset("2", "numval", "2");
client.hset("3", "numval", "3");
assertEquals(2, client.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, client.ftSearch(index, "@numval:[$min $max]",
FTSearchParams.searchParams().params(paramValues)
.dialect(2)).getTotalResults());
if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4) ) {
assertEquals(1, client.ftSearch(index, "@numval:[$eq]",
FTSearchParams.searchParams().addParam("eq", 2).dialect(4)).getTotalResults());
}
}
@Test
public void testSortQueryFlags() {
assertOK(client.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 = client.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(client.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);
client.jsonSet("king:1", Path.ROOT_PATH, king1);
Map<String, Object> king2 = new HashMap<>();
king2.put("name", "james");
king2.put("num", 3.14);
client.jsonSet("king:2", Path.ROOT_PATH, king2);
SearchResult res = client.ftSearch(index, "@name:henry");
assertEquals(1, res.getTotalResults());
assertEquals("king:1", res.getDocuments().get(0).getId());
res = client.ftSearch(index, "@num:[0 10]");
assertEquals(1, res.getTotalResults());
assertEquals("king:2", res.getDocuments().get(0).getId());
res = client.ftSearch(index, "@num:[42 42]", FTSearchParams.searchParams());
assertEquals(1, res.getTotalResults());
assertEquals("king:1", res.getDocuments().get(0).getId());
if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {
res = client.ftSearch(index, "@num:[42]", FTSearchParams.searchParams().dialect(4));
assertEquals(1, res.getTotalResults());
assertEquals("king:1", res.getDocuments().get(0).getId());
}
}
@Test
public void dropIndex() {
assertOK(client.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 = client.ftSearch(index, "hello world");
assertEquals(100, res.getTotalResults());
assertEquals("OK", client.ftDropIndex(index));
try {
client.ftSearch(index, "hello world");
fail("Index should not exist.");
} catch (JedisDataException de) {
// toLowerCase - Error message updated to "No such index" with Redis 8.0.0
assertTrue(de.getMessage().toLowerCase().contains("no such index"));
}
assertEquals(100, client.dbSize());
}
@Test
public void dropIndexDD() {
assertOK(client.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 = client.ftSearch(index, "hello world");
assertEquals(100, res.getTotalResults());
assertEquals("OK", client.ftDropIndexDD(index));
Set<String> keys = client.keys("*");
assertTrue(keys.isEmpty());
assertEquals(0, client.dbSize());
}
@Test
public void noStem() {
assertOK(client.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 = client.ftSearch(index, "@stemmed:location");
assertEquals(1, res.getTotalResults());
res = client.ftSearch(index, "@notStemmed:location");
assertEquals(0, res.getTotalResults());
}
@Test
public void phoneticMatch() {
assertOK(client.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 = client.ftSearch(index, "@withPhonetic:morphix=>{$phonetic:true}");
assertEquals(1, res.getTotalResults());
try {
client.ftSearch(index, "@noPhonetic:morphix=>{$phonetic:true}");
fail();
} catch (JedisDataException e) {/*field does not support phonetics*/
}
SearchResult res3 = client.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(client.ftCreate(index, sc));
Map<String, Object> info = client.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(client.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 = client.ftSearch(index, "@f1:Mark*");
assertEquals(0, res.getTotalResults());
res = client.ftSearch(index, "@f2:Mark*");
assertEquals(2, res.getTotalResults());
res = client.ftSearch(index, "@f2:Mark*",
FTSearchParams.searchParams().sortBy("f1", SortingOrder.DESC));
assertEquals(2, res.getTotalResults());
assertEquals("doc1", res.getDocuments().get(0).getId());
res = client.ftSearch(index, "@f2:Mark*",
FTSearchParams.searchParams().sortBy("f1", SortingOrder.ASC));
assertEquals("doc2", res.getDocuments().get(0).getId());
}
@Test
public void testHighlightSummarize() {
assertOK(client.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 = client.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 = client.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(client.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, client.ftSearch(index, "@category:{red}").getTotalResults());
assertEquals(1, client.ftSearch(index, "@category:{blue}").getTotalResults());
assertEquals(1, client.ftSearch(index, "hello @category:{red}").getTotalResults());
assertEquals(1, client.ftSearch(index, "hello @category:{blue}").getTotalResults());
assertEquals(1, client.ftSearch(index, "@category:{yellow}").getTotalResults());
assertEquals(0, client.ftSearch(index, "@category:{purple}").getTotalResults());
assertEquals(1, client.ftSearch(index, "@category:{orange\\;purple}").getTotalResults());
assertEquals(4, client.ftSearch(index, "hello").getTotalResults());
assertEquals(new HashSet<>(Arrays.asList("red", "blue", "green", "yellow", "orange;purple")),
client.ftTagVals(index, "category"));
}
@Test
public void testGetTagFieldWithNonDefaultSeparator() {
assertOK(client.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, client.ftSearch(index, "@category:{red}").getTotalResults());
assertEquals(1, client.ftSearch(index, "@category:{blue}").getTotalResults());
assertEquals(1, client.ftSearch(index, "hello @category:{red}").getTotalResults());
assertEquals(1, client.ftSearch(index, "hello @category:{blue}").getTotalResults());
assertEquals(1, client.ftSearch(index, "hello @category:{yellow}").getTotalResults());
assertEquals(0, client.ftSearch(index, "@category:{purple}").getTotalResults());
assertEquals(1, client.ftSearch(index, "@category:{orange\\,purple}").getTotalResults());
assertEquals(4, client.ftSearch(index, "hello").getTotalResults());
assertEquals(new HashSet<>(Arrays.asList("red", "blue", "green", "yellow", "orange,purple")),
client.ftTagVals(index, "category"));
}
@Test
public void caseSensitiveTagField() {
assertOK(client.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, client.ftSearch(index, "@category:{redx}").getTotalResults());
assertEquals(0, client.ftSearch(index, "@category:{redX}").getTotalResults());
assertEquals(0, client.ftSearch(index, "@category:{Redx}").getTotalResults());
assertEquals(1, client.ftSearch(index, "@category:{RedX}").getTotalResults());
assertEquals(1, client.ftSearch(index, "hello").getTotalResults());
}
@Test
public void tagFieldParams() {
assertOK(client.ftCreate("testindex", TextField.of("title"),
TagField.of("category").as("cat").separator(',')
.caseSensitive().withSuffixTrie().sortable()));
assertOK(client.ftCreate("testunfindex", TextField.of("title"),
TagField.of("category").as("cat").separator(',')
.caseSensitive().withSuffixTrie().sortableUNF()));
if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4)) {
assertOK(client.ftCreate("testindex-missing", TextField.of("title"),
TagField.of("category").as("cat").indexMissing().indexEmpty().separator(',')
.caseSensitive().withSuffixTrie().sortable()));
assertOK(client.ftCreate("testunfindex-missing", TextField.of("title"),
TagField.of("category").as("cat").indexMissing().indexEmpty().separator(',')
.caseSensitive().withSuffixTrie().sortableUNF()));
}
assertOK(client.ftCreate("testnoindex", TextField.of("title"),
TagField.of("category").as("cat").sortable().noIndex()));
assertOK(client.ftCreate("testunfnoindex", TextField.of("title"),
TagField.of("category").as("cat").sortableUNF().noIndex()));
}
@Test
public void returnFields() {
assertOK(client.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 = client.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 = client.ftSearch(index, "*", FTSearchParams.searchParams().returnField("field1", true));
assertEquals("value1", res.getDocuments().get(0).get("field1"));
res = client.ftSearch(index, "*", FTSearchParams.searchParams().returnField("field1", false));
assertArrayEquals("value1".getBytes(), (byte[]) res.getDocuments().get(0).get("field1"));
}
@Test
public void returnFieldsNames() {
assertOK(client.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 = client.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 = client.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 = client.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 = client.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(client.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 = client.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 alias() {
assertOK(client.ftCreate(index, TextField.of("field1")));
Map<String, Object> doc = new HashMap<>();
doc.put("field1", "value");
addDocument("doc1", doc);
assertEquals("OK", client.ftAliasAdd("ALIAS1", index));
SearchResult res1 = client.ftSearch("ALIAS1", "*",
FTSearchParams.searchParams().returnFields("field1"));
assertEquals(1, res1.getTotalResults());
assertEquals("value", res1.getDocuments().get(0).get("field1"));
assertEquals("OK", client.ftAliasUpdate("ALIAS2", index));
SearchResult res2 = client.ftSearch("ALIAS2", "*",
FTSearchParams.searchParams().returnFields("field1"));
assertEquals(1, res2.getTotalResults());
assertEquals("value", res2.getDocuments().get(0).get("field1"));
try {
client.ftAliasDel("ALIAS3");
fail("Should throw JedisDataException");
} catch (JedisDataException e) {
// Alias does not exist
}
assertEquals("OK", client.ftAliasDel("ALIAS2"));
try {
client.ftAliasDel("ALIAS2");
fail("Should throw JedisDataException");
} catch (JedisDataException e) {
// Alias does not exist
}
}
@Test
public void synonym() {
assertOK(client.ftCreate(index, TextField.of("name").weight(1), TextField.of("addr").weight(1)));
long group1 = 345L;
long group2 = 789L;
String group1_str = Long.toString(group1);
String group2_str = Long.toString(group2);
assertEquals("OK", client.ftSynUpdate(index, group1_str, "girl", "baby"));
assertEquals("OK", client.ftSynUpdate(index, group1_str, "child"));
assertEquals("OK", client.ftSynUpdate(index, group2_str, "child"));
Map<String, List<String>> dump = client.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 slop() {
assertOK(client.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 = client.ftSearch(index, "ok jedis", FTSearchParams.searchParams().slop(0));
assertEquals(0, res.getTotalResults());
res = client.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(client.ftCreate(index, TextField.of("field1"), TextField.of("field2")));
Map<String, String> map = new HashMap<>();
map.put("field1", "value");
map.put("field2", "not");
client.hset("doc1", map);
SearchResult res = client.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(client.ftCreate(index, TextField.of("field1"), TextField.of("field2")));
Map<String, String> map = new HashMap<>();
map.put("field1", "value");
map.put("field2", "not");
client.hset("doc2", map);
client.hset("doc1", map);
SearchResult res = client.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(client.ftCreate(index, VectorField.builder().fieldName("v")
.algorithm(VectorAlgorithm.HNSW).attributes(attr).build()));
client.hset("a", "v", "aaaaaaaa");
client.hset("b", "v", "aaaabaaa");
client.hset("c", "v", "aaaaabaa");
FTSearchParams searchParams = FTSearchParams.searchParams()
.addParam("vec", "aaaaaaaa")
.sortBy("__v_score", SortingOrder.ASC)
.returnFields("__v_score")
.dialect(2);
Document doc1 = client.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(client.ftCreate(index,
VectorField.builder().fieldName("v")
.algorithm(VectorAlgorithm.FLAT)
.addAttribute("TYPE", "FLOAT32")
.addAttribute("DIM", 2)
.addAttribute("DISTANCE_METRIC", "L2")
.build()
));
client.hset("a", "v", "aaaaaaaa");
client.hset("b", "v", "aaaabaaa");
client.hset("c", "v", "aaaaabaa");
FTSearchParams searchParams = FTSearchParams.searchParams()
.addParam("vec", "aaaaaaaa")
.sortBy("__v_score", SortingOrder.ASC)
.returnFields("__v_score")
.dialect(2);
Document doc1 = client.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(client).moduleVersionIsGreaterThanOrEqual(SEARCH_MOD_VER_80M3),
"INT8");
assertOK(client.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 };
client.hset("a".getBytes(), "v".getBytes(), a);
client.hset("b".getBytes(), "v".getBytes(), b);
client.hset("c".getBytes(), "v".getBytes(), c);
FTSearchParams searchParams = FTSearchParams.searchParams().addParam("vec", a)
.sortBy("__v_score", SortingOrder.ASC).returnFields("__v_score");
Document doc1 = client.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(client.ftCreate("testindex-missing", new VectorField("vector", VectorAlgorithm.HNSW, attr).as("vec").indexMissing()));
// assertOK(client.ftCreate("testnoindex", new VectorField("vector", VectorAlgorithm.HNSW, attr).as("vec").noIndex()));
// throws Field `NOINDEX` does not have a type
}
@Test
@SinceRedisVersion(value = "7.4.0", message = "FLOAT16")
public void float16StorageType() {
assertOK(client.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(client.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(client).moduleVersionIsGreaterThanOrEqual(SEARCH_MOD_VER_80M3),
"INT8");
assertOK(client.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(client).moduleVersionIsGreaterThanOrEqual(SEARCH_MOD_VER_80M3),
"UINT8");
assertOK(client.ftCreate(index,
VectorField.builder().fieldName("v").algorithm(VectorAlgorithm.HNSW)
.addAttribute("TYPE", "UINT8").addAttribute("DIM", 4)
.addAttribute("DISTANCE_METRIC", "L2").build()));
}
@Test
public void searchProfile() {
assertOK(client.ftCreate(index, TextField.of("t1"), TextField.of("t2")));
Map<String, String> hash = new HashMap<>();
hash.put("t1", "foo");
hash.put("t2", "bar");
client.hset("doc1", hash);
Map.Entry<SearchResult, ProfilingInfo> reply = client.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(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {
assertThat((List<Object>) profileObject, Matchers.hasItems("Shards", "Coordinator"));
}
} else {
assertThat(profileObject, Matchers.isA(Map.class));
if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {
assertThat(((Map<String, Object>) profileObject).keySet(), Matchers.hasItems("Shards", "Coordinator"));
}
}
}
@Test
public void vectorSearchProfile() {
assertOK(client.ftCreate(index, VectorField.builder().fieldName("v")
.algorithm(VectorAlgorithm.FLAT).addAttribute("TYPE", "FLOAT32")
.addAttribute("DIM", 2).addAttribute("DISTANCE_METRIC", "L2").build(),
TextField.of("t")));
client.hset("1", toMap("v", "bababaca", "t", "hello"));
client.hset("2", toMap("v", "babababa", "t", "hello"));
client.hset("3", toMap("v", "aabbaabb", "t", "hello"));
client.hset("4", toMap("v", "bbaabbaa", "t", "hello world"));
client.hset("5", toMap("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 = client.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(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {
assertThat((List<Object>) profileObject, Matchers.hasItems("Shards", "Coordinator"));
}
} else {
assertThat(profileObject, Matchers.isA(Map.class));
if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {
assertThat(((Map<String, Object>) profileObject).keySet(), Matchers.hasItems("Shards", "Coordinator"));
}
}
}
@Test
public void limitedSearchProfile() {
assertOK(client.ftCreate(index, TextField.of("t")));
client.hset("1", "t", "hello");
client.hset("2", "t", "hell");
client.hset("3", "t", "help");
client.hset("4", "t", "helowa");
Map.Entry<SearchResult, ProfilingInfo> reply = client.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(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {
assertThat((List<Object>) profileObject, Matchers.hasItems("Shards", "Coordinator"));
}
} else {
assertThat(profileObject, Matchers.isA(Map.class));
if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V8_0_0)) {
assertThat(((Map<String, Object>) profileObject).keySet(), Matchers.hasItems("Shards", "Coordinator"));
}
}
}
@Test
public void list() {
assertEquals(Collections.emptySet(), client.ftList());
final int count = 20;
Set<String> names = new HashSet<>();
for (int i = 0; i < count; i++) {
final String name = "idx" + i;
assertOK(client.ftCreate(name, TextField.of("t" + i)));
names.add(name);
}
assertEquals(names, client.ftList());
}
@Test
public void broadcast() {
String reply = client.ftCreate(index, TextField.of("t"));
assertOK(reply);
}
@Test
public void searchIteration() {
assertOK(client.ftCreate(index, FTCreateParams.createParams(),
TextField.of("first"), TextField.of("last"), NumericField.of("age")));
client.hset("profesor:5555", toMap("first", "Albert", "last", "Blue", "age", "55"));
client.hset("student:1111", toMap("first", "Joe", "last", "Dod", "age", "18"));
client.hset("pupil:2222", toMap("first", "Jen", "last", "Rod", "age", "14"));
client.hset("student:3333", toMap("first", "El", "last", "Mark", "age", "17"));
client.hset("pupil:4444", toMap("first", "Pat", "last", "Shu", "age", "21"));
client.hset("student:5555", toMap("first", "Joen", "last", "Ko", "age", "20"));
client.hset("teacher:6666", toMap("first", "Pat", "last", "Rod", "age", "20"));
FtSearchIteration search = client.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(client.ftCreate(index, FTCreateParams.createParams(),
TextField.of("first"), TextField.of("last"), NumericField.of("age")));
client.hset("profesor:5555", toMap("first", "Albert", "last", "Blue", "age", "55"));
client.hset("student:1111", toMap("first", "Joe", "last", "Dod", "age", "18"));
client.hset("pupil:2222", toMap("first", "Jen", "last", "Rod", "age", "14"));
client.hset("student:3333", toMap("first", "El", "last", "Mark", "age", "17"));
client.hset("pupil:4444", toMap("first", "Pat", "last", "Shu", "age", "21"));
client.hset("student:5555", toMap("first", "Joen", "last", "Ko", "age", "20"));
client.hset("teacher:6666", toMap("first", "Pat", "last", "Rod", "age", "20"));
ArrayList<Document> collect = new ArrayList<>();
client.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(client.ftCreate(index, TextField.of("txt")));
client.hset("doc1", "txt", RediSearchUtil.escape("hello-world"));
assertNotEquals("hello-world", client.hget("doc1", "txt"));
assertEquals("hello-world", RediSearchUtil.unescape(client.hget("doc1", "txt")));
SearchResult resultNoEscape = client.ftSearch(index, "hello-world");
assertEquals(0, resultNoEscape.getTotalResults());
SearchResult resultEscaped = client.ftSearch(index, RediSearchUtil.escapeQuery("hello-world"));
assertEquals(1, resultEscaped.getTotalResults());
}
@Test
public void escapeMapUtil() {
client.hset("doc2", RediSearchUtil.toStringMap(Collections.singletonMap("txt", "hello-world"), true));
assertNotEquals("hello-world", client.hget("doc2", "txt"));
assertEquals("hello-world", RediSearchUtil.unescape(client.hget("doc2", "txt")));
}
@Test
public void hsetObject() {
float[] floats = new float[]{0.2f};
assertEquals(1L, client.hsetObject("obj", "floats", floats));
assertArrayEquals(RediSearchUtil.toByteArray(floats),
client.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, client.hsetObject("obj", fields));
Map<String, String> stringMap = client.hgetAll("obj");
assertEquals(3, stringMap.size());
assertEquals("hello world", stringMap.get("title"));
assertEquals(geo.getLongitude() + "," + geo.getLatitude(), stringMap.get("loc"));
}
}