diff --git a/src/main/java/redis/clients/jedis/search/Query.java b/src/main/java/redis/clients/jedis/search/Query.java index a11030b84c..e21588cf16 100644 --- a/src/main/java/redis/clients/jedis/search/Query.java +++ b/src/main/java/redis/clients/jedis/search/Query.java @@ -158,6 +158,7 @@ public HighlightTags(String open, String close) { private boolean wantsSummarize = false; private String _scorer = null; private Map _params = null; + private int _dialect = 0; public Query() { this("*"); @@ -303,6 +304,11 @@ public void addParams(CommandArguments args) { args.add(entry.getValue()); } } + + if (_dialect != 0) { + args.add(SearchKeyword.DIALECT.getRaw()); + args.add(_dialect); + } } private static class DelayedRawable implements Rawable { @@ -548,4 +554,15 @@ public Query addParam(String name, Object value) { _params.put(name, value); return this; } + + /** + * Set the dialect version to execute the query accordingly + * + * @param dialect integer + * @return the query object itself + */ + public Query dialect(int dialect) { + _dialect = dialect; + return this; + } } diff --git a/src/main/java/redis/clients/jedis/search/SearchProtocol.java b/src/main/java/redis/clients/jedis/search/SearchProtocol.java index 8b02d3d7b0..d2e2583773 100644 --- a/src/main/java/redis/clients/jedis/search/SearchProtocol.java +++ b/src/main/java/redis/clients/jedis/search/SearchProtocol.java @@ -46,7 +46,7 @@ public enum SearchKeyword implements Rawable { ASC, DESC, PAYLOAD, LIMIT, HIGHLIGHT, FIELDS, TAGS, SUMMARIZE, FRAGS, LEN, SEPARATOR, INKEYS, RETURN, /*NOSAVE, PARTIAL, REPLACE,*/ FILTER, GEOFILTER, INCR, MAX, FUZZY, DD, /*DELETE,*/ DEL, READ, COUNT, ADD, TEMPORARY, STOPWORDS, NOFREQS, NOFIELDS, NOOFFSETS, /*IF,*/ SET, GET, ON, - ASYNC, PREFIX, LANGUAGE_FIELD, SCORE_FIELD, SCORE, PAYLOAD_FIELD, SCORER, PARAMS; + ASYNC, PREFIX, LANGUAGE_FIELD, SCORE_FIELD, SCORE, PAYLOAD_FIELD, SCORER, PARAMS, DIALECT; private final byte[] raw; diff --git a/src/test/java/redis/clients/jedis/modules/search/SearchTest.java b/src/test/java/redis/clients/jedis/modules/search/SearchTest.java index 0013e89d29..7894cf87d9 100644 --- a/src/test/java/redis/clients/jedis/modules/search/SearchTest.java +++ b/src/test/java/redis/clients/jedis/modules/search/SearchTest.java @@ -1,5 +1,6 @@ package redis.clients.jedis.modules.search; +import static java.util.Collections.singletonMap; import static org.junit.Assert.*; import static redis.clients.jedis.search.RediSearchUtil.toStringMap; @@ -451,10 +452,11 @@ public void testHNSWVVectorSimilarity() { client.hset("b", "v", "aaaabaaa"); client.hset("c", "v", "aaaaabaa"); - Query query = new Query("*=>[KNN 2 @v $vec]") + Query query = new Query("*=>[KNN 2 @v $vec]") .addParam("vec", "aaaaaaaa") .setSortBy("__v_score", true) - .returnFields("__v_score"); + .returnFields("__v_score") + .dialect(2); Document doc1 = client.ftSearch(index, query).getDocuments().get(0); assertEquals("a", doc1.getId()); assertEquals("0", doc1.get("__v_score")); @@ -474,15 +476,139 @@ public void testFlatVectorSimilarity() { client.hset("b", "v", "aaaabaaa"); client.hset("c", "v", "aaaaabaa"); - Query query = new Query("*=>[KNN 2 @v $vec]") + Query query = new Query("*=>[KNN 2 @v $vec]") .addParam("vec", "aaaaaaaa") .setSortBy("__v_score", true) - .returnFields("__v_score"); + .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 testDialectConfig() { + // confirm default + assertEquals(singletonMap("DEFAULT_DIALECT", "1"), client.ftConfigGet("DEFAULT_DIALECT")); + + assertEquals("OK", client.ftConfigSet("DEFAULT_DIALECT", "2")); + assertEquals(singletonMap("DEFAULT_DIALECT", "2"), client.ftConfigGet("DEFAULT_DIALECT")); + + try { + client.ftConfigSet("DEFAULT_DIALECT", "0"); + fail(); + } catch (JedisDataException ex) { + } + + try { + client.ftConfigSet("DEFAULT_DIALECT", "3"); + fail(); + } catch (JedisDataException ex) { + } + + // Restore to default + assertEquals("OK", client.ftConfigSet("DEFAULT_DIALECT", "1")); + } + + @Test + public void testDialectsWithFTExplain() throws Exception { + Map 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); + try { + client.ftExplain(index, query); + fail(); + } catch (JedisDataException e) { + assertTrue("Should contain 'Syntax error'", e.getMessage().contains("Syntax error")); + } + query = new Query(q).dialect(2); + assertTrue("Should contain 'WILDCARD'", client.ftExplain(index, query).contains("WILDCARD")); + + q = "$hello"; + query = new Query(q).dialect(1); + try { + client.ftExplain(index, query); + fail(); + } catch (JedisDataException e) { + assertTrue("Should contain 'Syntax error'", e.getMessage().contains("Syntax error")); + } + query = new Query(q).dialect(2).addParam("hello", "hello"); + assertTrue("Should contain 'UNION {\n hello\n +hello(expanded)\n}\n'", + client.ftExplain(index, query).contains("UNION {\n hello\n +hello(expanded)\n}\n")); + + q = "@title:(@num:[0 10])"; + query = new Query(q).dialect(1); + assertTrue("Should contain 'NUMERIC {0.000000 <= @num <= 10.000000}'", + client.ftExplain(index, query).contains("NUMERIC {0.000000 <= @num <= 10.000000}")); + query = new Query(q).dialect(2); + try { + client.ftExplain(index, query); + fail(); + } catch (JedisDataException e) { + assertTrue("Should contain 'Syntax error'", e.getMessage().contains("Syntax error")); + } + + q = "@t1:@t2:@t3:hello"; + query = new Query(q).dialect(1); + assertTrue("Should contain '@NULL:UNION {\n @NULL:hello\n @NULL:+hello(expanded)\n}\n'", + client.ftExplain(index, query).contains("@NULL:UNION {\n @NULL:hello\n @NULL:+hello(expanded)\n}\n")); + query = new Query(q).dialect(2); + try { + client.ftExplain(index, query); + fail(); + } catch (JedisDataException e) { + assertTrue("Should contain 'Syntax error'", e.getMessage().contains("Syntax error")); + } + + q = "@title:{foo}}}}}"; + query = new Query(q).dialect(1); + assertTrue("Should contain 'TAG:@title {\n foo\n}\n'", + client.ftExplain(index, query).contains("TAG:@title {\n foo\n}\n")); + query = new Query(q).dialect(2); + try { + client.ftExplain(index, query); + fail(); + } catch (JedisDataException e) { + assertTrue("Should contain 'Syntax error'", e.getMessage().contains("Syntax error")); + } + + q = "*=>[KNN 10 @v $BLOB]"; + query = new Query(q).addParam("BLOB", "aaaa").dialect(1); + try { + client.ftExplain(index, query); + fail(); + } catch (JedisDataException e) { + assertTrue("Should contain 'Syntax error'", e.getMessage().contains("Syntax error")); + } + query = new Query(q).addParam("BLOB", "aaaa").dialect(2); + assertTrue("Should contain '{K=10 nearest vector'", client.ftExplain(index, query).contains("{K=10 nearest vector")); + + q = "*=>[knn $K @vec_field $BLOB as score]"; + query = new Query(q).addParam("BLOB", "aaaa").addParam("K", "10").dialect(1); + try { + client.ftExplain(index, query); + fail(); + } catch (JedisDataException e) { + assertTrue("Should contain 'Syntax error'", e.getMessage().contains("Syntax error")); + } + query = new Query(q).addParam("BLOB", "aaaa").addParam("K", "10").dialect(2); + assertTrue("Should contain '{K=10 nearest vector'", client.ftExplain(index, query).contains("{K=10 nearest vector")); + } + @Test public void testQueryParams() { Schema sc = new Schema().addNumericField("numval"); @@ -492,7 +618,7 @@ public void testQueryParams() { client.hset("2", "numval", "2"); client.hset("3", "numval", "3"); - Query query = new Query("@numval:[$min $max]").addParam("min", 1).addParam("max", 2); + Query query = new Query("@numval:[$min $max]").addParam("min", 1).addParam("max", 2).dialect(2); assertEquals(2, client.ftSearch(index, query).getTotalResults()); } @@ -1218,8 +1344,8 @@ public void returnWithFieldNames() throws Exception { addDocument("doc", map); // Query - SearchResult res = client.ftSearch(index, new Query() - .returnFields(FieldName.of("a"), FieldName.of("b").as("d"))); + 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")); @@ -1285,7 +1411,7 @@ public void config() throws Exception { @Test public void configOnTimeout() throws Exception { assertEquals("OK", client.ftConfigSet("ON_TIMEOUT", "fail")); - assertEquals(Collections.singletonMap("ON_TIMEOUT", "fail"), client.ftConfigGet("ON_TIMEOUT")); + assertEquals(singletonMap("ON_TIMEOUT", "fail"), client.ftConfigGet("ON_TIMEOUT")); try { client.ftConfigSet("ON_TIMEOUT", "null");