diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 42384c5c9b..3c541e497b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,18 +1,35 @@ # How to use Jedis Github Issue -* Github issues SHOULD ONLY BE USED to report bugs, and for DETAILED feature requests. Everything else belongs to the Jedis Google Group. +* Github issues SHOULD BE USED to report bugs and for DETAILED feature requests. Everything else belongs in the [Jedis Google Group](https://groups.google.com/g/jedis_redis) or [Jedis Github Discussions](https://github.com/redis/jedis/discussions). -Jedis Google Group address: - -https://groups.google.com/forum/?fromgroups#!forum/jedis_redis +Please post general questions to Google Groups or Github discussions. These can be closed without response when posted to Github issues. -Please post General questions to Google Group. It can be closed without answer when posted to Github issue. +# How to contribute by Pull Request + +1. Fork Jedis repo on github ([how to fork a repo](https://docs.github.com/en/get-started/quickstart/fork-a-repo)) +2. Create a topic branch (`git checkout -b my_branch`) +3. Push to your remote branch (`git push origin my_branch`) +4. Create a pull request on github ([how to create a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request)) + +Create a branch with meaningful name, and do not modify the master branch directly. + +Please add unit tests to validate your changes work, then ensure your changes pass all unit tests. + +# Jedis Test Environment + +Jedis unit tests run with the latest [Redis unstable branch](https://github.com/redis/redis/tree/unstable). +Please let them prepared and installed. + +Jedis unit tests use many Redis instances, so we use a ```Makefile``` to prepare environment. + +Start unit tests with ```make test```. +Set up test environments with ```make start```, tear down those environments with ```make stop``` and clean up the environment files with ```make cleanup```. # Some rules of Jedis source code ## Code Convention -* Jedis uses ```HBase Formatter``` introduced by [HBASE-5961](https://issues.apache.org/jira/browse/HBASE-5961) +* Jedis uses HBase Formatter introduced by [HBASE-5961](https://issues.apache.org/jira/browse/HBASE-5961) * You can import code style file (located to hbase-formatter.xml) to Eclipse, IntelliJ * line break by column count seems not working with IntelliJ * You can run ```make format``` anytime to reformat without IDEs @@ -30,27 +47,4 @@ Please post General questions to Google Group. It can be closed without answer w * Caution: use String.toBytes() directly will break GBK support! * boolean, int, long, double -> byte array : use Protocol.toByteArray() -# How to contribute by Pull Request - -1. Fork Jedis on github (https://help.github.com/articles/fork-a-repo/) -2. Create a topic branch (git checkout -b my_branch) -3. Push to your branch (git push origin my_branch) -4. Post a pull request on github (https://help.github.com/articles/creating-a-pull-request/) - -I recommend you to create branch with meaningful name, not modifying master branch directly. - -Please add unit tests in order to prove your modification works smoothly. And please make sure your modification passes all unit tests. - -# Jedis Test Environment - -Jedis unit tests run with latest [```Redis unstable branch```](https://github.com/antirez/redis). -Please let them prepared and installed. - -Jedis unit tests use many Redis instances, so we use ```Makefile``` to prepare environment. - -You can start test with ```make test```. -You can set up test environments by ```make start```, and tear down environments by ```make stop```. - -If one or some of unit tests in current master branch of Jedis fails with Redis unstable branch, please post it to Github issue, and go ahead with other unit tests at your work. - Thanks! diff --git a/.github/workflows/version-and-release.yml b/.github/workflows/version-and-release.yml index 636333f657..007d8875e8 100644 --- a/.github/workflows/version-and-release.yml +++ b/.github/workflows/version-and-release.yml @@ -1,9 +1,8 @@ name: Release on: - push: - tags: - - '*' + release: + types: [published] jobs: build: diff --git a/README.md b/README.md index d9b94c1652..4442b23bfe 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,14 @@ Jedis is a Java client for [Redis](https://github.com/redis/redis "Redis") designed for performance and ease of use. +## Contributing + +We'd love your contributions! + +**Bug reports** are always welcome! [You can open a bug report on GitHub](https://github.com/redis/jedis/issues/new). + +You can also **contribute documentation** -- or anything to improve Jedis. Please see [contribution guideline](https://github.com/redis/jedis/blob/master/.github/CONTRIBUTING.md) for more details. + ## Getting started To get started with Jedis, first add it as a dependency in your Java project. If you're using Maven, that looks like this: @@ -21,7 +29,7 @@ To get started with Jedis, first add it as a dependency in your Java project. If redis.clients jedis - 4.1.1 + 4.2.0 ``` @@ -76,7 +84,7 @@ jedis.sadd("planets", "Mars"); ## Using Redis modules -Jedis provides support for some of the [Redis modules](https://redis.io/modules), most notably [RedisJSON](https://oss.redis.com/redisjson/) and [RediSearch](https://oss.redis.com/redisearch/). +Jedis provides support for some of the [Redis modules](https://redis.io/docs/modules/), most notably [RedisJSON](https://oss.redis.com/redisjson/) and [RediSearch](https://oss.redis.com/redisearch/). See the [RedisJSON Jedis Quick Start](docs/redisjson.md) for details. @@ -94,19 +102,10 @@ Hit us up on the [Redis Discord Server](http://discord.gg/redis) or [open an iss You can also find help on the [Jedis mailing list](http://groups.google.com/group/jedis_redis) or the [GitHub Discussions](https://github.com/redis/jedis/discussions). -## Contributing - -We'd love your contributions! - -**Bug reports** are always welcome! [You can open a bug report on GitHub](https://github.com/redis/jedis/issues/new). - -You can also **contribute documentation** -- or anything to improve Jedis. Please see [CONTRIBUTING.md](https://github.com/redis/jedis/blob/master/.github/CONTRIBUTING.md) for more details. - ## License Jedis is licensed under the [MIT license](https://github.com/redis/jedis/blob/master/LICENSE.txt). - ## Sponsorship [![Redis Logo](redis-logo-full-color-rgb.png)](https://redis.com/) diff --git a/pom.xml b/pom.xml index a58f0c279a..062bd4a55a 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ jar redis.clients jedis - 4.2.0-SNAPSHOT + 4.3.0-SNAPSHOT Jedis Jedis is a blazingly small and sane Redis java client. https://github.com/redis/jedis 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");