org.mockito
mockito-inline
diff --git a/src/main/java/redis/clients/jedis/search/SearchProtocol.java b/src/main/java/redis/clients/jedis/search/SearchProtocol.java
index 36780b569b..7f2ad482fb 100644
--- a/src/main/java/redis/clients/jedis/search/SearchProtocol.java
+++ b/src/main/java/redis/clients/jedis/search/SearchProtocol.java
@@ -49,13 +49,13 @@ public byte[] getRaw() {
public enum SearchKeyword implements Rawable {
- SCHEMA, TEXT, TAG, NUMERIC, GEO, VECTOR, VERBATIM, NOCONTENT, NOSTOPWORDS, WITHSCORES, LANGUAGE,
- INFIELDS, SORTBY, ASC, DESC, LIMIT, HIGHLIGHT, FIELDS, TAGS, SUMMARIZE, FRAGS, LEN, SEPARATOR,
- INKEYS, RETURN, FILTER, GEOFILTER, ADD, INCR, MAX, FUZZY, READ, DEL, DD, TEMPORARY, STOPWORDS,
- NOFREQS, NOFIELDS, NOOFFSETS, NOHL, SET, GET, ON, SORTABLE, UNF, PREFIX, LANGUAGE_FIELD, SCORE,
- SCORE_FIELD, SCORER, PARAMS, AS, DIALECT, SLOP, TIMEOUT, INORDER, EXPANDER, MAXTEXTFIELDS,
- SKIPINITIALSCAN, WITHSUFFIXTRIE, NOSTEM, NOINDEX, PHONETIC, WEIGHT, CASESENSITIVE,
- LOAD, APPLY, GROUPBY, MAXIDLE, WITHCURSOR, DISTANCE, TERMS, INCLUDE, EXCLUDE,
+ SCHEMA, TEXT, TAG, NUMERIC, GEO, GEOSHAPE, VECTOR, VERBATIM, NOCONTENT, NOSTOPWORDS, WITHSCORES,
+ LANGUAGE, INFIELDS, SORTBY, ASC, DESC, LIMIT, HIGHLIGHT, FIELDS, TAGS, SUMMARIZE, FRAGS, LEN,
+ SEPARATOR, INKEYS, RETURN, FILTER, GEOFILTER, ADD, INCR, MAX, FUZZY, READ, DEL, DD, TEMPORARY,
+ STOPWORDS, NOFREQS, NOFIELDS, NOOFFSETS, NOHL, SET, GET, ON, SORTABLE, UNF, PREFIX,
+ LANGUAGE_FIELD, SCORE, SCORE_FIELD, SCORER, PARAMS, AS, DIALECT, SLOP, TIMEOUT, INORDER,
+ EXPANDER, MAXTEXTFIELDS, SKIPINITIALSCAN, WITHSUFFIXTRIE, NOSTEM, NOINDEX, PHONETIC, WEIGHT,
+ CASESENSITIVE, LOAD, APPLY, GROUPBY, MAXIDLE, WITHCURSOR, DISTANCE, TERMS, INCLUDE, EXCLUDE,
SEARCH, AGGREGATE, QUERY, LIMITED, COUNT, REDUCE;
private final byte[] raw;
diff --git a/src/main/java/redis/clients/jedis/search/schemafields/GeoShapeField.java b/src/main/java/redis/clients/jedis/search/schemafields/GeoShapeField.java
new file mode 100644
index 0000000000..dd3b45e59e
--- /dev/null
+++ b/src/main/java/redis/clients/jedis/search/schemafields/GeoShapeField.java
@@ -0,0 +1,49 @@
+package redis.clients.jedis.search.schemafields;
+
+import static redis.clients.jedis.search.SearchProtocol.SearchKeyword.GEOSHAPE;
+
+import redis.clients.jedis.CommandArguments;
+import redis.clients.jedis.search.FieldName;
+
+public class GeoShapeField extends SchemaField {
+
+ public enum CoordinateSystem {
+
+ /**
+ * For cartesian (X,Y).
+ */
+ FLAT,
+
+ /**
+ * For geographic (lon, lat).
+ */
+ SPHERICAL
+ }
+
+ private final CoordinateSystem system;
+
+ public GeoShapeField(String fieldName, CoordinateSystem system) {
+ super(fieldName);
+ this.system = system;
+ }
+
+ public GeoShapeField(FieldName fieldName, CoordinateSystem system) {
+ super(fieldName);
+ this.system = system;
+ }
+
+ public static GeoShapeField of(String fieldName, CoordinateSystem system) {
+ return new GeoShapeField(fieldName, system);
+ }
+
+ @Override
+ public GeoShapeField as(String attribute) {
+ super.as(attribute);
+ return this;
+ }
+
+ @Override
+ public void addParams(CommandArguments args) {
+ args.addParams(fieldName).add(GEOSHAPE).add(system);
+ }
+}
diff --git a/src/test/java/redis/clients/jedis/examples/GeoShapeFieldsUsageInRediSearch.java b/src/test/java/redis/clients/jedis/examples/GeoShapeFieldsUsageInRediSearch.java
new file mode 100644
index 0000000000..db4db2cb0f
--- /dev/null
+++ b/src/test/java/redis/clients/jedis/examples/GeoShapeFieldsUsageInRediSearch.java
@@ -0,0 +1,105 @@
+package redis.clients.jedis.examples;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.io.ParseException;
+import org.locationtech.jts.io.WKTReader;
+
+import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisPooled;
+import redis.clients.jedis.UnifiedJedis;
+import redis.clients.jedis.search.FTSearchParams;
+import redis.clients.jedis.search.SearchResult;
+import redis.clients.jedis.search.schemafields.GeoShapeField;
+
+import static java.util.Collections.singletonMap;
+import static org.junit.Assert.assertEquals;
+import static redis.clients.jedis.search.RediSearchUtil.toStringMap;
+
+/**
+ * As of RediSearch 2.8.4, advanced GEO querying with GEOSHAPE fields is supported.
+ *
+ * Any object/library producing a
+ * well-known
+ * text (WKT) in {@code toString()} method can be used.
+ *
+ * This example uses the JTS library.
+ *
+ * {@code
+ *
+ * org.locationtech.jts
+ * jts-core
+ * 1.19.0
+ *
+ * }
+ *
+ */
+public class GeoShapeFieldsUsageInRediSearch {
+
+ public static void main(String[] args) {
+
+ // We'll create geometry objects with GeometryFactory
+ final GeometryFactory factory = new GeometryFactory();
+
+ final String host = "localhost";
+ final int port = 6379;
+ final HostAndPort address = new HostAndPort(host, port);
+
+ UnifiedJedis client = new JedisPooled(address);
+ // client.setDefaultSearchDialect(3); // we can set default search dialect for the client (UnifiedJedis) object
+ // to avoid setting dialect in every query.
+
+ // creating index
+ client.ftCreate("geometry-index",
+ GeoShapeField.of("geometry", GeoShapeField.CoordinateSystem.SPHERICAL) // 'SPHERICAL' is for geographic (lon, lat).
+ // 'FLAT' coordinate system also available for cartesian (X,Y).
+ );
+
+ // preparing data
+ 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.hset("small", toStringMap(singletonMap("geometry", small))); // setting data
+
+ 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.hset("large", toStringMap(singletonMap("geometry", large))); // setting data
+
+ // searching
+ 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 res = client.ftSearch("geometry-index",
+ "@geometry:[within $poly]", // querying 'within' condition.
+ // RediSearch also supports 'contains' condition.
+ FTSearchParams.searchParams()
+ .addParam("poly", within)
+ .dialect(3) // DIALECT '3' is required for this query
+ );
+ assertEquals(1, res.getTotalResults());
+ assertEquals(1, res.getDocuments().size());
+
+ // We can parse geometry objects with WKTReader
+ try {
+ final WKTReader reader = new WKTReader();
+ Geometry object = reader.read(res.getDocuments().get(0).getString("geometry"));
+ assertEquals(small, object);
+ } catch (ParseException ex) {
+ ex.printStackTrace(System.err);
+ }
+ }
+
+ // Note: As of RediSearch 2.8.4, only POLYGON and POINT objects are supported.
+}
diff --git a/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java b/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java
index a63b625f50..7f152f8985 100644
--- a/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java
+++ b/src/test/java/redis/clients/jedis/modules/search/QueryBuilderTest.java
@@ -91,10 +91,10 @@ public void testIntersectionBasic() {
@Test
public void testIntersectionNested() {
- Node n = intersect().
- add(union("name", value("mark"), value("dvir"))).
- add("time", between(100, 200)).
- add(disjunct("created", lt(1000)));
+ Node n = intersect()
+ .add(union("name", value("mark"), value("dvir")))
+ .add("time", between(100, 200))
+ .add(disjunct("created", lt(1000)));
assertEquals("(@name:(mark|dvir) @time:[100 200] -@created:[-inf (1000])", n.toString());
}
diff --git a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java
index 4784eaa3d3..792391b775 100644
--- a/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java
+++ b/src/test/java/redis/clients/jedis/modules/search/SearchWithParamsTest.java
@@ -9,6 +9,13 @@
import org.junit.BeforeClass;
import org.junit.Test;
+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;
@@ -333,6 +340,100 @@ public void geoFilterAndGeoCoordinateObject() {
assertEquals(2, res.getTotalResults());
}
+ @Test
+ public void geoShapeFilterSpherical() throws ParseException {
+ final WKTReader reader = new WKTReader();
+ final GeometryFactory factory = new GeometryFactory();
+
+ assertOK(client.ftCreate(index, GeoShapeField.of("geom", GeoShapeField.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.hset("small", RediSearchUtil.toStringMap(Collections.singletonMap("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.hset("large", RediSearchUtil.toStringMap(Collections.singletonMap("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 res = client.ftSearch(index, "@geom:[within $poly]",
+ FTSearchParams.searchParams().addParam("poly", within).dialect(3));
+ assertEquals(1, res.getTotalResults());
+ assertEquals(1, res.getDocuments().size());
+ assertEquals(small, reader.read(res.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)});
+
+ res = client.ftSearch(index, "@geom:[contains $poly]",
+ FTSearchParams.searchParams().addParam("poly", contains).dialect(3));
+ assertEquals(2, res.getTotalResults());
+ assertEquals(2, res.getDocuments().size());
+
+ // point type
+ final Point point = factory.createPoint(new Coordinate(34.9010, 29.7010));
+ client.hset("point", RediSearchUtil.toStringMap(Collections.singletonMap("geom", point)));
+
+ res = client.ftSearch(index, "@geom:[within $poly]",
+ FTSearchParams.searchParams().addParam("poly", within).dialect(3));
+ assertEquals(2, res.getTotalResults());
+ assertEquals(2, res.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", GeoShapeField.CoordinateSystem.FLAT)));
+
+ // polygon type
+ final Polygon small = factory.createPolygon(new Coordinate[]{new Coordinate(1, 1),
+ new Coordinate(1, 100), new Coordinate(100, 100), new Coordinate(100, 1), new Coordinate(1, 1)});
+ client.hset("small", RediSearchUtil.toStringMap(Collections.singletonMap("geom", small)));
+
+ final Polygon large = factory.createPolygon(new Coordinate[]{new Coordinate(1, 1),
+ new Coordinate(1, 200), new Coordinate(200, 200), new Coordinate(200, 1), new Coordinate(1, 1)});
+ client.hset("large", RediSearchUtil.toStringMap(Collections.singletonMap("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 res = client.ftSearch(index, "@geom:[within $poly]",
+ FTSearchParams.searchParams().addParam("poly", within).dialect(3));
+ assertEquals(1, res.getTotalResults());
+ assertEquals(1, res.getDocuments().size());
+ assertEquals(small, reader.read(res.getDocuments().get(0).getString("geom")));
+
+ // contains condition
+ final Polygon contains = factory.createPolygon(new Coordinate[]{new Coordinate(2, 2),
+ new Coordinate(2, 50), new Coordinate(50, 50), new Coordinate(50, 2), new Coordinate(2, 2)});
+
+ res = client.ftSearch(index, "@geom:[contains $poly]",
+ FTSearchParams.searchParams().addParam("poly", contains).dialect(3));
+ assertEquals(2, res.getTotalResults());
+ assertEquals(2, res.getDocuments().size());
+
+ // point type
+ final Point point = factory.createPoint(new Coordinate(10, 10));
+ client.hset("point", RediSearchUtil.toStringMap(Collections.singletonMap("geom", point)));
+
+ res = client.ftSearch(index, "@geom:[within $poly]",
+ FTSearchParams.searchParams().addParam("poly", within).dialect(3));
+ assertEquals(2, res.getTotalResults());
+ assertEquals(2, res.getDocuments().size());
+ }
+
@Test
public void testQueryFlags() {
assertOK(client.ftCreate(index, TextField.of("title")));