diff --git a/src/Command/Argument/Search/SchemaFields/GeoShapeField.php b/src/Command/Argument/Search/SchemaFields/GeoShapeField.php new file mode 100644 index 00000000..26d9df2c --- /dev/null +++ b/src/Command/Argument/Search/SchemaFields/GeoShapeField.php @@ -0,0 +1,57 @@ +fieldArguments[] = $identifier; + + if ($alias !== '') { + $this->fieldArguments[] = 'AS'; + $this->fieldArguments[] = $alias; + } + + $this->fieldArguments[] = 'GEOSHAPE'; + + if (null !== $coordSystem) { + $this->fieldArguments[] = $coordSystem; + } + + if ($sortable === self::SORTABLE) { + $this->fieldArguments[] = 'SORTABLE'; + } elseif ($sortable === self::SORTABLE_UNF) { + $this->fieldArguments[] = 'SORTABLE'; + $this->fieldArguments[] = 'UNF'; + } + + if ($noIndex) { + $this->fieldArguments[] = 'NOINDEX'; + } + } +} diff --git a/tests/Predis/Command/Argument/Search/SchemaFields/GeoShapeFieldTest.php b/tests/Predis/Command/Argument/Search/SchemaFields/GeoShapeFieldTest.php new file mode 100644 index 00000000..63603694 --- /dev/null +++ b/tests/Predis/Command/Argument/Search/SchemaFields/GeoShapeFieldTest.php @@ -0,0 +1,61 @@ +assertSame($expectedSchema, (new GeoShapeField(...$arguments))->toArray()); + } + + public function geoFieldsProvider(): array + { + return [ + 'with default arguments' => [ + ['field_name'], + ['field_name', 'GEOSHAPE'], + ], + 'with alias' => [ + ['field_name', 'fn'], + ['field_name', 'AS', 'fn', 'GEOSHAPE'], + ], + 'with sortable - no UNF' => [ + ['field_name', '', AbstractField::SORTABLE], + ['field_name', 'GEOSHAPE', 'SORTABLE'], + ], + 'with sortable - with UNF' => [ + ['field_name', '', AbstractField::SORTABLE_UNF], + ['field_name', 'GEOSHAPE', 'SORTABLE', 'UNF'], + ], + 'with NOINDEX modifier' => [ + ['field_name', '', AbstractField::NOT_SORTABLE, true], + ['field_name', 'GEOSHAPE', 'NOINDEX'], + ], + 'with FLAT modifier' => [ + ['field_name', '', AbstractField::NOT_SORTABLE, false, GeoShapeField::COORD_FLAT], + ['field_name', 'GEOSHAPE', 'FLAT'], + ], + ]; + } +} diff --git a/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php b/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php index f7c690eb..503e541f 100644 --- a/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php +++ b/tests/Predis/Command/Redis/Search/FTSEARCH_Test.php @@ -13,6 +13,8 @@ namespace Predis\Command\Redis\Search; use Predis\Command\Argument\Search\CreateArguments; +use Predis\Command\Argument\Search\SchemaFields\AbstractField; +use Predis\Command\Argument\Search\SchemaFields\GeoShapeField; use Predis\Command\Argument\Search\SchemaFields\NumericField; use Predis\Command\Argument\Search\SchemaFields\TextField; use Predis\Command\Argument\Search\SearchArguments; @@ -125,6 +127,123 @@ class FTSEARCH_Test extends PredisCommandTestCase $this->assertSame($expectedResponse, $actualResponse); } + /** + * @group connected + * @group relay-resp3 + * @return void + * @requiresRediSearchVersion >= 2.9.0 + */ + public function testGeoSearchQueriesIntersectsAndDisjoint(): void + { + $redis = $this->getClient(); + + $redis->hset('geo:doc_point1', 'g', 'POINT (10 10)'); + $redis->hset('geo:doc_point2', 'g', 'POINT (50 50)'); + $redis->hset('geo:doc_polygon1', 'g', 'POLYGON ((20 20, 25 35, 35 25, 20 20))'); + $redis->hset('geo:doc_polygon2', 'g', 'POLYGON ((60 60, 65 75, 70 70, 65 55, 60 60))'); + + $ftCreateArguments = new CreateArguments(); + $ftCreateArguments->prefix(['geo:']); + + $schema = [ + new GeoShapeField('g', '', AbstractField::NOT_SORTABLE, false, GeoShapeField::COORD_FLAT), + ]; + + $ftCreateResponse = $redis->ftcreate('idx_geo', $schema, $ftCreateArguments); + $this->assertEquals('OK', $ftCreateResponse); + + $ftSearchArguments = new SearchArguments(); + $ftSearchArguments->params(['shape', 'POLYGON ((15 15, 75 15, 50 70, 20 40, 15 15))']); + $ftSearchArguments->noContent(); + $ftSearchArguments->dialect(3); + + $actualResponse = $redis->ftsearch('idx_geo', '@g:[intersects $shape]', $ftSearchArguments); + $this->assertSameValues( + [ + 2, + 'geo:doc_polygon1', + 'geo:doc_point2', + ], $actualResponse + ); + + $actualResponse = $redis->ftsearch('idx_geo', '@g:[disjoint $shape]', $ftSearchArguments); + $this->assertSameValues( + [ + 2, + 'geo:doc_polygon2', + 'geo:doc_point1', + ], $actualResponse + ); + } + + /** + * @group connected + * @group relay-resp3 + * @return void + * @requiresRediSearchVersion >= 2.9.0 + */ + public function testGeoSearchQueriesContainsAndWithin(): void + { + $redis = $this->getClient(); + + $redis->hset('geo:doc_point1', 'g', 'POINT (10 10)'); + $redis->hset('geo:doc_point2', 'g', 'POINT (50 50)'); + $redis->hset('geo:doc_polygon1', 'g', 'POLYGON ((20 20, 25 35, 35 25, 20 20))'); + $redis->hset('geo:doc_polygon2', 'g', 'POLYGON ((60 60, 65 75, 70 70, 65 55, 60 60))'); + + $ftCreateArguments = new CreateArguments(); + $ftCreateArguments->prefix(['geo:']); + + $schema = [ + new GeoShapeField('g', '', + AbstractField::NOT_SORTABLE, false, GeoShapeField::COORD_FLAT + ), + ]; + + $ftCreateResponse = $redis->ftcreate('idx_geo', $schema, $ftCreateArguments); + $this->assertEquals('OK', $ftCreateResponse); + + $ftSearchArguments = new SearchArguments(); + $ftSearchArguments->params(['shape', 'POINT(25 25)']); + $ftSearchArguments->noContent(); + $ftSearchArguments->dialect(3); + + $actualResponse = $redis->ftsearch('idx_geo', '@g:[contains $shape]', $ftSearchArguments); + $this->assertSameValues( + [ + 1, + 'geo:doc_polygon1', + ], $actualResponse + ); + + $ftSearchArguments = new SearchArguments(); + $ftSearchArguments->params(['shape', 'POLYGON((24 24, 24 26, 25 25, 24 24))']); + $ftSearchArguments->noContent(); + $ftSearchArguments->dialect(3); + + $actualResponse = $redis->ftsearch('idx_geo', '@g:[contains $shape]', $ftSearchArguments); + $this->assertSameValues( + [ + 1, + 'geo:doc_polygon1', + ], $actualResponse + ); + + $ftSearchArguments = new SearchArguments(); + $ftSearchArguments->params(['shape', 'POLYGON((15 15, 75 15, 50 70, 20 40, 15 15))']); + $ftSearchArguments->noContent(); + $ftSearchArguments->dialect(3); + + $actualResponse = $redis->ftsearch('idx_geo', '@g:[within $shape]', $ftSearchArguments); + $this->assertSameValues( + [ + 2, + 'geo:doc_polygon1', + 'geo:doc_point2', + ], $actualResponse + ); + } + public function argumentsProvider(): array { return [