SearchTest.java
package redis.clients.jedis.modules.search;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.emptyOrNullString;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
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.assumeFalse;
import java.util.*;
import java.util.stream.Collectors;
import io.redis.test.utils.RedisVersion;
import org.hamcrest.Matchers;
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 redis.clients.jedis.RedisProtocol;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.exceptions.JedisDataException;
import redis.clients.jedis.json.Path;
import redis.clients.jedis.search.*;
import redis.clients.jedis.search.Schema.*;
import redis.clients.jedis.modules.RedisModuleCommandsTestBase;
import redis.clients.jedis.util.RedisVersionUtil;
import redis.clients.jedis.util.SafeEncoder;
@ParameterizedClass
@MethodSource("redis.clients.jedis.commands.CommandsTestsParameters#respVersions")
public class SearchTest extends RedisModuleCommandsTestBase {
private static final String index = "testindex";
@BeforeAll
public static void prepare() {
RedisModuleCommandsTestBase.prepare();
}
public SearchTest(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() throws Exception {
Schema sc = new Schema().addTextField("first", 1.0).addTextField("last", 1.0).addNumericField("age");
IndexDefinition rule = new IndexDefinition()
.setFilter("@age>16")
.setPrefixes(new String[]{"student:", "pupil:"});
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), sc));
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, new Query());
assertEquals(4, noFilters.getTotalResults());
SearchResult res1 = client.ftSearch(index, new Query("@first:Jo*"));
assertEquals(2, res1.getTotalResults());
SearchResult res2 = client.ftSearch(index, new Query("@first:Pat"));
assertEquals(1, res2.getTotalResults());
SearchResult res3 = client.ftSearch(index, new Query("@last:Rod"));
assertEquals(0, res3.getTotalResults());
}
@Test
public void createNoParams() {
Schema sc = new Schema().addTextField("first", 1.0).addTextField("last", 1.0).addNumericField("age");
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
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, new Query());
assertEquals(4, noFilters.getTotalResults());
SearchResult res1 = client.ftSearch(index, new Query("@first:Jo*"));
assertEquals(2, res1.getTotalResults());
SearchResult res2 = client.ftSearch(index, new Query("@first:Pat"));
assertEquals(1, res2.getTotalResults());
SearchResult res3 = client.ftSearch(index, new Query("@last:Rod"));
assertEquals(0, res3.getTotalResults());
}
@Test
public void createWithFieldNames() {
Schema sc = new Schema().addField(new TextField(FieldName.of("first").as("given")))
.addField(new TextField(FieldName.of("last")));
IndexDefinition rule = new IndexDefinition()
//.setFilter("@age>16")
.setPrefixes(new String[]{"student:", "pupil:"});
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), sc));
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, new Query());
assertEquals(5, noFilters.getTotalResults());
SearchResult asAttribute = client.ftSearch(index, new Query("@given:Jo*"));
assertEquals(2, asAttribute.getTotalResults());
SearchResult nonAttribute = client.ftSearch(index, new Query("@last:Rod"));
assertEquals(1, nonAttribute.getTotalResults());
}
@Test
public void alterAdd() {
Schema sc = new Schema().addTextField("title", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
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, new Query("hello world"));
assertEquals(100, res.getTotalResults());
assertEquals("OK", client.ftAlter(index, new TagField("tags", ","), new TextField("name", 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));
// assertTrue(client.updateDocument(String.format("doc%d", i), 1.0, fields2));
addDocument(String.format("doc%d", i), fields2);
}
SearchResult res2 = client.ftSearch(index, new Query("@tags:{tagA}"));
assertEquals(100, res2.getTotalResults());
}
@Test
public void search() throws Exception {
Schema sc = new Schema().addTextField("title", 1.0).addTextField("body", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
fields.put("body", "lorem ipsum");
for (int i = 0; i < 100; i++) {
// assertTrue(client.addDocument(String.format("doc%d", i), (double) i / 100.0, fields));
addDocument(String.format("doc%d", i), fields);
}
SearchResult res = client.ftSearch(index, new Query("hello world").limit(0, 5).setWithScores());
assertEquals(100, res.getTotalResults());
assertEquals(5, res.getDocuments().size());
for (Document d : res.getDocuments()) {
assertTrue(d.getId().startsWith("doc"));
assertTrue(d.getScore() < 100);
// assertEquals(
// String.format(
// "{\"id\":\"%s\",\"score\":%s,\"properties\":{\"title\":\"hello world\",\"body\":\"lorem ipsum\"}}",
// d.getId(), Double.toString(d.getScore())),
// d.toString());
}
// assertTrue(client.deleteDocument("doc0", true));
// assertFalse(client.deleteDocument("doc0"));
client.del("doc0");
res = client.ftSearch(index, new Query("hello world"));
assertEquals(99, res.getTotalResults());
assertEquals("OK", client.ftDropIndex(index));
try {
client.ftSearch(index, new Query("hello world"));
fail();
} catch (JedisDataException e) {
}
}
@Test
public void numericFilter() throws Exception {
Schema sc = new Schema().addTextField("title", 1.0).addNumericField("price");
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
for (int i = 0; i < 100; i++) {
fields.put("price", i);
// assertTrue(client.addDocument(String.format("doc%d", i), fields));
addDocument(String.format("doc%d", i), fields);
}
SearchResult res = client.ftSearch(index, new Query("hello world").
addFilter(new Query.NumericFilter("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, new Query("hello world").
addFilter(new Query.NumericFilter("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, new Query("hello world").
addFilter(new Query.NumericFilter("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, new Query("hello world").
addFilter(new Query.NumericFilter("price", 20, Double.POSITIVE_INFINITY)));
assertEquals(80, res.getTotalResults());
assertEquals(10, res.getDocuments().size());
res = client.ftSearch(index, new Query("hello world").
addFilter(new Query.NumericFilter("price", Double.NEGATIVE_INFINITY, 10)));
assertEquals(11, res.getTotalResults());
assertEquals(10, res.getDocuments().size());
}
@Test
public void stopwords() {
Schema sc = new Schema().addTextField("title", 1.0);
assertEquals("OK", client.ftCreate(index,
IndexOptions.defaultOptions().setStopwords("foo", "bar", "baz"), sc));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world foo bar");
// assertTrue(client.addDocument("doc1", fields));
addDocument("doc1", fields);
SearchResult res = client.ftSearch(index, new Query("hello world"));
assertEquals(1, res.getTotalResults());
res = client.ftSearch(index, new Query("foo bar"));
assertEquals(0, res.getTotalResults());
}
@Test
public void noStopwords() {
Schema sc = new Schema().addTextField("title", 1.0);
assertEquals("OK", client.ftCreate(index,
IndexOptions.defaultOptions().setNoStopwords(), sc));
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, new Query("hello world")).getTotalResults());
assertEquals(1, client.ftSearch(index, new Query("foo bar")).getTotalResults());
assertEquals(1, client.ftSearch(index, new Query("to be or not to be")).getTotalResults());
}
@Test
public void geoFilter() {
Schema sc = new Schema().addTextField("title", 1.0).addGeoField("loc");
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
fields.put("loc", "-0.441,51.458");
// assertTrue(client.addDocument("doc1", fields));
addDocument("doc1", fields);
fields.put("loc", "-0.1,51.2");
// assertTrue(client.addDocument("doc2", fields));
addDocument("doc2", fields);
SearchResult res = client.ftSearch(index, new Query("hello world").
addFilter(
new Query.GeoFilter("loc", -0.44, 51.45,
10, Query.GeoFilter.KILOMETERS)
));
assertEquals(1, res.getTotalResults());
res = client.ftSearch(index, new Query("hello world").
addFilter(
new Query.GeoFilter("loc", -0.44, 51.45,
100, Query.GeoFilter.KILOMETERS)
));
assertEquals(2, res.getTotalResults());
}
@Test
public void geoFilterAndGeoCoordinateObject() {
Schema schema = new Schema().addTextField("title", 1.0).addGeoField("loc");
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), schema));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
fields.put("loc", new redis.clients.jedis.GeoCoordinate(-0.441, 51.458));
// assertTrue(client.addDocument("doc1", fields));
addDocument("doc1", fields);
fields.put("loc", new redis.clients.jedis.GeoCoordinate(-0.1, 51.2));
// assertTrue(client.addDocument("doc2", fields));
addDocument("doc2", fields);
SearchResult res = client.ftSearch(index, new Query("hello world").addFilter(
new Query.GeoFilter("loc", -0.44, 51.45, 10, Query.GeoFilter.KILOMETERS)));
assertEquals(1, res.getTotalResults());
res = client.ftSearch(index, new Query("hello world").addFilter(
new Query.GeoFilter("loc", -0.44, 51.45, 100, Query.GeoFilter.KILOMETERS)));
assertEquals(2, res.getTotalResults());
}
@Test
public void testQueryFlags() {
Schema sc = new Schema().addTextField("title", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> fields = new HashMap<>();
for (int i = 0; i < 100; i++) {
fields.put("title", i % 2 != 0 ? "hello worlds" : "hello world");
// assertTrue(client.addDocument(String.format("doc%d", i), (double) i / 100.0, fields));
addDocument(String.format("doc%d", i), fields);
}
Query q = new Query("hello").setWithScores();
SearchResult res = client.ftSearch(index, q);
assertEquals(100, res.getTotalResults());
assertEquals(10, res.getDocuments().size());
for (Document d : res.getDocuments()) {
assertTrue(d.getId().startsWith("doc"));
// assertNotEquals(1.0, d.getScore());
assertTrue(((String) d.get("title")).startsWith("hello world"));
}
q = new Query("hello").setNoContent();
res = client.ftSearch(index, q);
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, new Query("hello worlds"));
assertEquals(100, res.getTotalResults());
res = client.ftSearch(index, new Query("hello worlds").setVerbatim());
assertEquals(50, res.getTotalResults());
res = client.ftSearch(index, new Query("hello a world").setVerbatim());
assertEquals(50, res.getTotalResults());
res = client.ftSearch(index, new Query("hello a worlds").setVerbatim());
assertEquals(50, res.getTotalResults());
res = client.ftSearch(index, new Query("hello a world").setVerbatim().setNoStopwords());
assertEquals(0, res.getTotalResults());
}
@Test
public void testQueryParams() {
Schema sc = new Schema().addNumericField("numval");
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
client.hset("1", "numval", "1");
client.hset("2", "numval", "2");
client.hset("3", "numval", "3");
Query query = new Query("@numval:[$min $max]").addParam("min", 1).addParam("max", 2).dialect(2);
assertEquals(2, client.ftSearch(index, query).getTotalResults());
if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4) ) {
query = new Query("@numval:[$eq]").addParam("eq", 2).dialect(4);
assertEquals(1, client.ftSearch(index, query).getTotalResults());
}
}
@Test
public void testSortQueryFlags() {
Schema sc = new Schema().addSortableTextField("title", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "b title");
// client.addDocument("doc1", 1.0, fields, false, true, null);
addDocument("doc1", fields);
fields.put("title", "a title");
// client.addDocument("doc2", 1.0, fields, false, true, null);
addDocument("doc2", fields);
fields.put("title", "c title");
// client.addDocument("doc3", 1.0, fields, false, true, null);
addDocument("doc3", fields);
Query q = new Query("title").setSortBy("title", true);
SearchResult res = client.ftSearch(index, q);
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 testNullField() {
Schema sc = new Schema()
.addTextField("title", 1.0)
.addTextField("genre", 1.0)
.addTextField("plot", 1.0)
.addSortableNumericField("release_year")
.addTagField("tag")
.addGeoField("loc");
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
// create a document with a field set to null
Map<String, Object> fields = new HashMap<>();
fields.put("title", "another test with title ");
fields.put("genre", "Comedy");
fields.put("plot", "this is the plot for the test");
fields.put("tag", "fun");
fields.put("release_year", 2019);
fields.put("loc", "-0.1,51.2");
// client.addDocument("doc1", fields);
addDocument("doc1", fields);
SearchResult res = client.ftSearch(index, new Query("title"));
assertEquals(1, res.getTotalResults());
fields = new HashMap<>();
fields.put("title", "another title another test");
fields.put("genre", "Action");
fields.put("plot", null);
fields.put("tag", null);
try {
// client.addDocument("doc2", fields);
addDocument("doc2", fields);
fail("Should throw NullPointerException.");
} catch (NullPointerException e) {
// assertEquals("Document attribute 'tag' is null. (Remove it, or set a value)", e.getMessage());
}
res = client.ftSearch(index, new Query("title"));
assertEquals(1, res.getTotalResults());
// Testing with numerical value
fields = new HashMap<>();
fields.put("title", "another title another test");
fields.put("genre", "Action");
fields.put("release_year", null);
try {
// client.addDocument("doc2", fields);
addDocument("doc2", fields);
fail("Should throw NullPointerException.");
} catch (NullPointerException e) {
// assertEquals("Document attribute 'release_year' is null. (Remove it, or set a value)", e.getMessage());
}
res = client.ftSearch(index, new Query("title"));
assertEquals(1, res.getTotalResults());
}
@Test
public void testJsonWithAlias() {
Schema sc = new Schema()
.addTextField("$.name", 1.0).as("name")
.addNumericField("$.num").as("num");
IndexDefinition definition = new IndexDefinition(IndexDefinition.Type.JSON).setPrefixes("king:");
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(definition), sc));
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, new Query("@name:henry"));
assertEquals(1, res.getTotalResults());
assertEquals("king:1", res.getDocuments().get(0).getId());
res = client.ftSearch(index, new Query("@num:[0 10]"));
assertEquals(1, res.getTotalResults());
assertEquals("king:2", res.getDocuments().get(0).getId());
res = client.ftSearch(index, new Query("@num:[42 42]"));
assertEquals(1, res.getTotalResults());
assertEquals("king:1", res.getDocuments().get(0).getId());
if (RedisVersionUtil.getRedisVersion(client).isGreaterThanOrEqualTo(RedisVersion.V7_4) ) {
res = client.ftSearch(index, new Query("@num:[42]").dialect(4));
assertEquals(1, res.getTotalResults());
assertEquals("king:1", res.getDocuments().get(0).getId());
}
}
@Test
public void dropIndex() {
Schema sc = new Schema().addTextField("title", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
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, new Query("hello world"));
assertEquals(100, res.getTotalResults());
assertEquals("OK", client.ftDropIndex(index));
try {
client.ftSearch(index, new Query("hello world"));
fail("Index should not exist.");
} catch (JedisDataException de) {
// 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() {
Schema sc = new Schema().addTextField("title", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
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, new Query("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() {
Schema sc = new Schema().addTextField("stemmed", 1.0).addField(new Schema.TextField("notStemmed", 1.0, false, true));
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> doc = new HashMap<>();
doc.put("stemmed", "located");
doc.put("notStemmed", "located");
// Store it
// assertTrue(client.addDocument("doc", doc));
addDocument("doc", doc);
// Query
SearchResult res = client.ftSearch(index, new Query("@stemmed:location"));
assertEquals(1, res.getTotalResults());
res = client.ftSearch(index, new Query("@notStemmed:location"));
assertEquals(0, res.getTotalResults());
}
@Test
public void phoneticMatch() {
Schema sc = new Schema()
.addTextField("noPhonetic", 1.0)
.addField(new Schema.TextField("withPhonetic", 1.0, false, false, false, "dm:en"));
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> doc = new HashMap<>();
doc.put("noPhonetic", "morfix");
doc.put("withPhonetic", "morfix");
// Store it
// assertTrue(client.addDocument("doc", doc));
addDocument("doc", doc);
// Query
SearchResult res = client.ftSearch(index, new Query("@withPhonetic:morphix=>{$phonetic:true}"));
assertEquals(1, res.getTotalResults());
try {
client.ftSearch(index, new Query("@noPhonetic:morphix=>{$phonetic:true}"));
fail();
} catch (JedisDataException e) {/*field does not support phonetics*/
}
SearchResult res3 = client.ftSearch(index, new Query("@withPhonetic:morphix=>{$phonetic:false}"));
assertEquals(0, res3.getTotalResults());
}
@Test
public void info() throws Exception {
String MOVIE_ID = "movie_id";
String TITLE = "title";
String GENRE = "genre";
String VOTES = "votes";
String RATING = "rating";
String RELEASE_YEAR = "release_year";
String PLOT = "plot";
String POSTER = "poster";
Schema sc = new Schema()
.addTextField(TITLE, 5.0)
.addSortableTextField(PLOT, 1.0)
.addSortableTagField(GENRE, ",")
.addSortableNumericField(RELEASE_YEAR)
.addSortableNumericField(RATING)
.addSortableNumericField(VOTES);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), 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"));
assertEquals(128L, ((Map) info.get("cursor_stats")).get("index_capacity"));
}
}
@Test
public void noIndex() {
Schema sc = new Schema()
.addField(new Schema.TextField("f1", 1.0, true, false, true))
.addField(new Schema.TextField("f2", 1.0));
client.ftCreate(index, IndexOptions.defaultOptions(), sc);
Map<String, Object> mm = new HashMap<>();
mm.put("f1", "MarkZZ");
mm.put("f2", "MarkZZ");
// client.addDocument("doc1", mm);
addDocument("doc1", mm);
mm.clear();
mm.put("f1", "MarkAA");
mm.put("f2", "MarkBB");
// client.addDocument("doc2", mm);
addDocument("doc2", mm);
SearchResult res = client.ftSearch(index, new Query("@f1:Mark*"));
assertEquals(0, res.getTotalResults());
res = client.ftSearch(index, new Query("@f2:Mark*"));
assertEquals(2, res.getTotalResults());
Document[] docs = new Document[2];
res = client.ftSearch(index, new Query("@f2:Mark*").setSortBy("f1", false));
assertEquals(2, res.getTotalResults());
res.getDocuments().toArray(docs);
assertEquals("doc1", docs[0].getId());
res = client.ftSearch(index, new Query("@f2:Mark*").setSortBy("f1", true));
res.getDocuments().toArray(docs);
assertEquals("doc2", docs[0].getId());
}
@Test
public void testExplain() {
Schema sc = new Schema()
.addTextField("f1", 1.0)
.addTextField("f2", 1.0)
.addTextField("f3", 1.0);
client.ftCreate(index, IndexOptions.defaultOptions(), sc);
String res = client.ftExplain(index, new Query("@f3:f3_val @f2:f2_val @f1:f1_val"));
assertNotNull(res);
assertFalse(res.isEmpty());
}
@Test
public void testHighlightSummarize() {
Schema sc = new Schema().addTextField("text", 1.0);
client.ftCreate(index, IndexOptions.defaultOptions(), sc);
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
// client.addDocument("foo", 1.0, doc);
addDocument("foo", doc);
Query q = new Query("data").highlightFields().summarizeFields();
SearchResult res = client.ftSearch(index, q);
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"));
q = new Query("data").highlightFields(new Query.HighlightTags("<u>", "</u>")).summarizeFields();
res = client.ftSearch(index, q);
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() {
Schema sc = new Schema()
.addTextField("title", 1.0)
.addTagField("category");
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> fields1 = new HashMap<>();
fields1.put("title", "hello world");
fields1.put("category", "red");
// assertTrue(client.addDocument("foo", fields1));
addDocument("foo", fields1);
Map<String, Object> fields2 = new HashMap<>();
fields2.put("title", "hello world");
fields2.put("category", "blue");
// assertTrue(client.addDocument("bar", fields2));
addDocument("bar", fields2);
Map<String, Object> fields3 = new HashMap<>();
fields3.put("title", "hello world");
fields3.put("category", "green,yellow");
// assertTrue(client.addDocument("baz", fields3));
addDocument("baz", fields3);
Map<String, Object> fields4 = new HashMap<>();
fields4.put("title", "hello world");
fields4.put("category", "orange;purple");
// assertTrue(client.addDocument("qux", fields4));
addDocument("qux", fields4);
assertEquals(1, client.ftSearch(index, new Query("@category:{red}")).getTotalResults());
assertEquals(1, client.ftSearch(index, new Query("@category:{blue}")).getTotalResults());
assertEquals(1, client.ftSearch(index, new Query("hello @category:{red}")).getTotalResults());
assertEquals(1, client.ftSearch(index, new Query("hello @category:{blue}")).getTotalResults());
assertEquals(1, client.ftSearch(index, new Query("@category:{yellow}")).getTotalResults());
assertEquals(0, client.ftSearch(index, new Query("@category:{purple}")).getTotalResults());
assertEquals(1, client.ftSearch(index, new Query("@category:{orange\\;purple}")).getTotalResults());
assertEquals(4, client.ftSearch(index, new Query("hello")).getTotalResults());
assertEquals(new HashSet<>(Arrays.asList("red", "blue", "green", "yellow", "orange;purple")),
client.ftTagVals(index, "category"));
}
@Test
public void testGetTagFieldWithNonDefaultSeparator() {
Schema sc = new Schema()
.addTextField("title", 1.0)
.addTagField("category", ";");
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> fields1 = new HashMap<>();
fields1.put("title", "hello world");
fields1.put("category", "red");
// assertTrue(client.addDocument("foo", fields1));
addDocument("foo", fields1);
Map<String, Object> fields2 = new HashMap<>();
fields2.put("title", "hello world");
fields2.put("category", "blue");
// assertTrue(client.addDocument("bar", fields2));
addDocument("bar", fields2);
Map<String, Object> fields3 = new HashMap<>();
fields3.put("title", "hello world");
fields3.put("category", "green;yellow");
addDocument("baz", fields3);
// assertTrue(client.addDocument("baz", fields3));
Map<String, Object> fields4 = new HashMap<>();
fields4.put("title", "hello world");
fields4.put("category", "orange,purple");
// assertTrue(client.addDocument("qux", fields4));
addDocument("qux", fields4);
assertEquals(1, client.ftSearch(index, new Query("@category:{red}")).getTotalResults());
assertEquals(1, client.ftSearch(index, new Query("@category:{blue}")).getTotalResults());
assertEquals(1, client.ftSearch(index, new Query("hello @category:{red}")).getTotalResults());
assertEquals(1, client.ftSearch(index, new Query("hello @category:{blue}")).getTotalResults());
assertEquals(1, client.ftSearch(index, new Query("hello @category:{yellow}")).getTotalResults());
assertEquals(0, client.ftSearch(index, new Query("@category:{purple}")).getTotalResults());
assertEquals(1, client.ftSearch(index, new Query("@category:{orange\\,purple}")).getTotalResults());
assertEquals(4, client.ftSearch(index, new Query("hello")).getTotalResults());
assertEquals(new HashSet<>(Arrays.asList("red", "blue", "green", "yellow", "orange,purple")),
client.ftTagVals(index, "category"));
}
@Test
public void caseSensitiveTagField() {
Schema sc = new Schema()
.addTextField("title", 1.0)
.addTagField("category", true /*casesensitive*/);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> fields = new HashMap<>();
fields.put("title", "hello world");
fields.put("category", "RedX");
addDocument("foo", fields);
assertEquals(0, client.ftSearch(index, new Query("@category:{redx}")).getTotalResults());
assertEquals(0, client.ftSearch(index, new Query("@category:{redX}")).getTotalResults());
assertEquals(0, client.ftSearch(index, new Query("@category:{Redx}")).getTotalResults());
assertEquals(1, client.ftSearch(index, new Query("@category:{RedX}")).getTotalResults());
assertEquals(1, client.ftSearch(index, new Query("hello")).getTotalResults());
}
@Test
public void testReturnFields() {
Schema sc = new Schema().addTextField("field1", 1.0).addTextField("field2", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> doc = new HashMap<>();
doc.put("field1", "value1");
doc.put("field2", "value2");
// Store it
// assertTrue(client.addDocument("doc", doc));
addDocument("doc", doc);
// Query
SearchResult res = client.ftSearch(index, new Query("*").returnFields("field1"));
assertEquals(1, res.getTotalResults());
assertEquals("value1", res.getDocuments().get(0).get("field1"));
assertNull(res.getDocuments().get(0).get("field2"));
}
@Test
public void returnWithFieldNames() {
Schema sc = new Schema().addTextField("a", 1).addTextField("b", 1).addTextField("c", 1);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> map = new HashMap<>();
map.put("a", "value1");
map.put("b", "value2");
map.put("c", "value3");
// assertTrue(client.addDocument("doc", map));
addDocument("doc", map);
// Query
SearchResult res = client.ftSearch(index,
new Query().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"));
}
@Test
public void inKeys() {
Schema sc = new Schema().addTextField("field1", 1.0).addTextField("field2", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> doc = new HashMap<>();
doc.put("field1", "value");
doc.put("field2", "not");
// Store it
// assertTrue(client.addDocument("doc1", doc));
addDocument("doc1", doc);
// assertTrue(client.addDocument("doc2", doc));
addDocument("doc2", doc);
// Query
SearchResult res = client.ftSearch(index, new Query("value").limitKeys("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 blobField() {
assumeFalse(protocol == RedisProtocol.RESP3); // not supporting
Schema sc = new Schema().addTextField("field1", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
byte[] blob = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
Map<String, Object> doc = new HashMap<>();
doc.put("field1", "value");
doc.put("field2", blob);
// Store it
// assertTrue(client.addDocument("doc1", doc));
addDocument("doc1", doc);
// Query
// SearchResult res = client.ftSearch(index, new Query("value"), false);
SearchResult res = client.ftSearch(SafeEncoder.encode(index), new Query("value"));
assertEquals(1, res.getTotalResults());
assertEquals("doc1", res.getDocuments().get(0).getId());
assertEquals("value", res.getDocuments().get(0).getString("field1"));
assertArrayEquals(blob, (byte[]) res.getDocuments().get(0).get("field2"));
}
@Test
public void alias() {
Schema sc = new Schema().addTextField("field1", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> doc = new HashMap<>();
doc.put("field1", "value");
// assertTrue(client.addDocument("doc1", doc));
addDocument("doc1", doc);
assertEquals("OK", client.ftAliasAdd("ALIAS1", index));
SearchResult res1 = client.ftSearch("ALIAS1", new Query("*").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", new Query("*").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() {
Schema sc = new Schema().addTextField("name", 1.0).addTextField("addr", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
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() {
Schema sc = new Schema().addTextField("field1", 1.0).addTextField("field2", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, Object> doc = new HashMap<>();
doc.put("field1", "ok hi jedis");
addDocument("doc1", doc);
SearchResult res = client.ftSearch(index, new Query("ok jedis").slop(0));
assertEquals(0, res.getTotalResults());
res = client.ftSearch(index, new Query("ok jedis").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() {
Schema sc = new Schema().addTextField("field1", 1.0).addTextField("field2", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
Map<String, String> map = new HashMap<>();
map.put("field1", "value");
map.put("field2", "not");
client.hset("doc1", map);
SearchResult res = client.ftSearch(index, new Query("value").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() {
Schema sc = new Schema().addTextField("field1", 1.0).addTextField("field2", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
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, new Query("value").setInOrder());
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 testDialectsWithFTExplain() {
Map<String, Object> attr = new HashMap<>();
attr.put("TYPE", "FLOAT32");
attr.put("DIM", 2);
attr.put("DISTANCE_METRIC", "L2");
Schema sc = new Schema()
.addFlatVectorField("v", attr)
.addTagField("title")
.addTextField("t1", 1.0)
.addTextField("t2", 1.0)
.addNumericField("num");
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
client.hset("1", "t1", "hello");
String q = "(*)";
Query query = new Query(q).dialect(1);
assertSyntaxError(query, client); // dialect=1 throws syntax error
query = new Query(q).dialect(2);
assertThat(client.ftExplain(index, query), containsString("WILDCARD"));
q = "$hello";
query = new Query(q).dialect(1);
assertSyntaxError(query, client); // dialect=1 throws syntax error
query = new Query(q).dialect(2).addParam("hello", "hello");
assertThat(client.ftExplain(index, query), not(emptyOrNullString()));
q = "@title:(@num:[0 10])";
query = new Query(q).dialect(1);
assertThat(client.ftExplain(index, query), not(emptyOrNullString()));
query = new Query(q).dialect(2);
assertSyntaxError(query, client); // dialect=2 throws syntax error
q = "@t1:@t2:@t3:hello";
query = new Query(q).dialect(1);
assertThat(client.ftExplain(index, query), not(emptyOrNullString()));
query = new Query(q).dialect(2);
assertSyntaxError(query, client); // dialect=2 throws syntax error
q = "@title:{foo}}}}}";
query = new Query(q).dialect(1);
assertThat(client.ftExplain(index, query), not(emptyOrNullString()));
query = new Query(q).dialect(2);
assertSyntaxError(query, client); // dialect=2 throws syntax error
q = "*=>[KNN 10 @v $BLOB]";
query = new Query(q).addParam("BLOB", "aaaa").dialect(1);
assertSyntaxError(query, client); // dialect=1 throws syntax error
query = new Query(q).addParam("BLOB", "aaaa").dialect(2);
assertThat(client.ftExplain(index, query), not(emptyOrNullString()));
q = "*=>[knn $K @v $BLOB as score]";
query = new Query(q).addParam("BLOB", "aaaa").addParam("K", "10").dialect(1);
assertSyntaxError(query, client); // dialect=1 throws syntax error
query = new Query(q).addParam("BLOB", "aaaa").addParam("K", "10").dialect(2);
assertThat(client.ftExplain(index, query), not(emptyOrNullString()));
}
@Test
public void searchProfile() {
Schema sc = new Schema().addTextField("t1", 1.0).addTextField("t2", 1.0);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
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(), new Query("foo"));
SearchResult result = reply.getKey();
assertEquals(1, result.getTotalResults());
assertEquals(Collections.singletonList("doc1"), result.getDocuments().stream().map(Document::getId).collect(Collectors.toList()));
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 testHNSWVVectorSimilarity() {
Map<String, Object> attr = new HashMap<>();
attr.put("TYPE", "FLOAT32");
attr.put("DIM", 2);
attr.put("DISTANCE_METRIC", "L2");
Schema sc = new Schema().addHNSWVectorField("v", attr);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
client.hset("a", "v", "aaaaaaaa");
client.hset("b", "v", "aaaabaaa");
client.hset("c", "v", "aaaaabaa");
Query query = new Query("*=>[KNN 2 @v $vec]")
.addParam("vec", "aaaaaaaa")
.setSortBy("__v_score", true)
.returnFields("__v_score")
.dialect(2);
Document doc1 = client.ftSearch(index, query).getDocuments().get(0);
assertEquals("a", doc1.getId());
assertEquals("0", doc1.get("__v_score"));
}
@Test
public void testFlatVectorSimilarity() {
Map<String, Object> attr = new HashMap<>();
attr.put("TYPE", "FLOAT32");
attr.put("DIM", 2);
attr.put("DISTANCE_METRIC", "L2");
Schema sc = new Schema().addFlatVectorField("v", attr);
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions(), sc));
client.hset("a", "v", "aaaaaaaa");
client.hset("b", "v", "aaaabaaa");
client.hset("c", "v", "aaaaabaa");
Query query = new Query("*=>[KNN 2 @v $vec]")
.addParam("vec", "aaaaaaaa")
.setSortBy("__v_score", true)
.returnFields("__v_score")
.dialect(2);
Document doc1 = client.ftSearch(index, query).getDocuments().get(0);
assertEquals("a", doc1.getId());
assertEquals("0", doc1.get("__v_score"));
}
@Test
public void searchIteration() {
Schema sc = new Schema().addTextField("first", 1.0).addTextField("last", 1.0).addNumericField("age");
IndexDefinition rule = new IndexDefinition();
assertEquals("OK", client.ftCreate(index, IndexOptions.defaultOptions().setDefinition(rule), sc));
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, new Query());
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);
}
void assertSyntaxError(Query query, UnifiedJedis client) {
JedisDataException error = assertThrows(JedisDataException.class,
() -> client.ftExplain(index, query));
assertThat(error.getMessage(), containsString("Syntax error"));
}
}