Hi,

This patch series is to add support for spgist quadtree @<(point,circle)
operator. The first two patches are to refactor existing code before
implemention the new feature. The third commit is the actual implementation
provided with a set of simple unit tests.

Changes since v2:
  - fix coding style
  - add comment to spg_quad_inner_consistent_circle_helper()
  - rework spg_quad_inner_consistent_circle_helper() using HYPOT() to make the
    search consistent with filter scan

Matwey V. Kornilov (3):
  Introduce helper variable in spgquadtreeproc.c
  Introduce spg_quad_inner_consistent_box_helper() in spgquadtreeproc.c
  Add initial support for spgist quadtree @<(point,circle) operator

 src/backend/access/spgist/spgquadtreeproc.c       | 147 +++++++++++++++-------
 src/include/catalog/pg_amop.dat                   |   3 +
 src/test/regress/expected/create_index_spgist.out |  96 ++++++++++++++
 src/test/regress/sql/create_index_spgist.sql      |  32 +++++
 4 files changed, 234 insertions(+), 44 deletions(-)

-- 
2.13.7

-- 
With best regards,
Matwey V. Kornilov
From 912a5d76107fc2597b951d2e2f3c22400a6c0cf6 Mon Sep 17 00:00:00 2001
From: "Matwey V. Kornilov" <matwey.korni...@gmail.com>
Date: Fri, 1 Feb 2019 12:14:18 +0300
Subject: [PATCH v2 2/3] Introduce spg_quad_inner_consistent_box_helper() in
 spgquadtreeproc.c

This helper function makes spg_quad_inner_consistent() more readable when
changes from the next commit is introduced.

Signed-off-by: Matwey V. Kornilov <matwey.korni...@gmail.com>
---
 src/backend/access/spgist/spgquadtreeproc.c | 64 ++++++++++++++---------------
 1 file changed, 32 insertions(+), 32 deletions(-)

diff --git a/src/backend/access/spgist/spgquadtreeproc.c b/src/backend/access/spgist/spgquadtreeproc.c
index f2e980b758..41e7fe5c81 100644
--- a/src/backend/access/spgist/spgquadtreeproc.c
+++ b/src/backend/access/spgist/spgquadtreeproc.c
@@ -112,6 +112,37 @@ getQuadrantArea(BOX *bbox, Point *centroid, int quadrant)
 	return result;
 }
 
+static int
+spg_quad_inner_consistent_box_helper(ScanKey sk, Point *centroid)
+{
+	/*
+	 * For this operator, the query is a box not a point.  We
+	 * cheat to the extent of assuming that DatumGetPointP won't
+	 * do anything that would be bad for a pointer-to-box.
+	 */
+	BOX *boxQuery = DatumGetBoxP(sk->sk_argument);
+	Point p;
+	int r = 0;
+
+	if (DatumGetBool(DirectFunctionCall2(box_contain_pt, PointerGetDatum(boxQuery), PointerGetDatum(centroid))))
+	{
+		/* centroid is in box, so all quadrants are OK */
+		return (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4);
+	}
+
+	/* identify quadrant(s) containing all corners of box */
+	p = boxQuery->low;
+	r |= 1 << getQuadrant(centroid, &p);
+	p.y = boxQuery->high.y;
+	r |= 1 << getQuadrant(centroid, &p);
+	p = boxQuery->high;
+	r |= 1 << getQuadrant(centroid, &p);
+	p.x = boxQuery->low.x;
+	r |= 1 << getQuadrant(centroid, &p);
+
+	return r;
+}
+
 Datum
 spg_quad_choose(PG_FUNCTION_ARGS)
 {
@@ -302,7 +333,6 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS)
 	{
 		const ScanKey sk = in->scankeys + i;
 		Point	   *query = DatumGetPointP(sk->sk_argument);
-		BOX		   *boxQuery;
 
 		switch (sk->sk_strategy)
 		{
@@ -326,37 +356,7 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS)
 					which &= (1 << 1) | (1 << 4);
 				break;
 			case RTContainedByStrategyNumber:
-
-				/*
-				 * For this operator, the query is a box not a point.  We
-				 * cheat to the extent of assuming that DatumGetPointP won't
-				 * do anything that would be bad for a pointer-to-box.
-				 */
-				boxQuery = DatumGetBoxP(sk->sk_argument);
-
-				if (DatumGetBool(DirectFunctionCall2(box_contain_pt,
-													 PointerGetDatum(boxQuery),
-													 PointerGetDatum(centroid))))
-				{
-					/* centroid is in box, so all quadrants are OK */
-				}
-				else
-				{
-					/* identify quadrant(s) containing all corners of box */
-					Point		p;
-					int			r = 0;
-
-					p = boxQuery->low;
-					r |= 1 << getQuadrant(centroid, &p);
-					p.y = boxQuery->high.y;
-					r |= 1 << getQuadrant(centroid, &p);
-					p = boxQuery->high;
-					r |= 1 << getQuadrant(centroid, &p);
-					p.x = boxQuery->low.x;
-					r |= 1 << getQuadrant(centroid, &p);
-
-					which &= r;
-				}
+				which &= spg_quad_inner_consistent_box_helper(sk, centroid);
 				break;
 			default:
 				elog(ERROR, "unrecognized strategy number: %d", sk->sk_strategy);
-- 
2.13.7

From 60bd01b8752def70685cfb67fe47bf0ae9be3f02 Mon Sep 17 00:00:00 2001
From: "Matwey V. Kornilov" <matwey.korni...@gmail.com>
Date: Thu, 31 Jan 2019 13:20:40 +0300
Subject: [PATCH v2 3/3] Add initial support for spgist quadtree
 @<(point,circle) operator

Signed-off-by: Matwey V. Kornilov <matwey.korni...@gmail.com>
---
 src/backend/access/spgist/spgquadtreeproc.c       | 73 +++++++++++++++--
 src/include/catalog/pg_amop.dat                   |  3 +
 src/test/regress/expected/create_index_spgist.out | 96 +++++++++++++++++++++++
 src/test/regress/sql/create_index_spgist.sql      | 32 ++++++++
 4 files changed, 197 insertions(+), 7 deletions(-)

diff --git a/src/backend/access/spgist/spgquadtreeproc.c b/src/backend/access/spgist/spgquadtreeproc.c
index 41e7fe5c81..4dd2246a92 100644
--- a/src/backend/access/spgist/spgquadtreeproc.c
+++ b/src/backend/access/spgist/spgquadtreeproc.c
@@ -143,6 +143,44 @@ spg_quad_inner_consistent_box_helper(ScanKey sk, Point *centroid)
 	return r;
 }
 
+static int
+spg_quad_inner_consistent_circle_helper(ScanKey sk, Point *centroid)
+{
+	/*
+	 * Evaluate distances between the query center point and the quadrants.
+	 * The distance between a set and a point is the minimal possible distance
+	 * between any set point and the point. There are three possible cases: the
+	 * point belongs to the quandrand, the point projection is on the quadrant
+	 * edge, the point projections is on the quadrant vertex.
+	 */
+	CIRCLE *circleQuery = DatumGetCircleP(sk->sk_argument);
+	int r = 0;
+
+	const Point cp = {
+		.x = float8_mi(centroid->x, circleQuery->center.x),
+		.y = float8_mi(centroid->y, circleQuery->center.y)
+	};
+
+	const float8 x_p0 = (0. > cp.x ? 0. : cp.x);
+	const float8 y_p0 = (0. > cp.y ? 0. : cp.y);
+	const float8 x_n0 = (0. < cp.x ? 0. : cp.x);
+	const float8 y_n0 = (0. < cp.y ? 0. : cp.y);
+
+	const float8 d[4] = {
+		HYPOT(x_p0, y_p0),
+		HYPOT(x_p0, y_n0),
+		HYPOT(x_n0, y_n0),
+		HYPOT(x_n0, y_p0)
+	};
+
+	for (int i = 0; i < 4; i++) {
+		if (d[i] <= circleQuery->radius)
+			r |= (1 << (i + 1));
+	}
+
+	return r;
+}
+
 Datum
 spg_quad_choose(PG_FUNCTION_ARGS)
 {
@@ -356,7 +394,18 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS)
 					which &= (1 << 1) | (1 << 4);
 				break;
 			case RTContainedByStrategyNumber:
-				which &= spg_quad_inner_consistent_box_helper(sk, centroid);
+
+				switch (sk->sk_subtype) {
+				case BOXOID:
+					which &= spg_quad_inner_consistent_box_helper(sk, centroid);
+					break;
+				case CIRCLEOID:
+					which &= spg_quad_inner_consistent_circle_helper(sk, centroid);
+					break;
+				default:
+					elog(ERROR, "unrecognized right type OID: %d", sk->sk_subtype);
+					break;
+				}
 				break;
 			default:
 				elog(ERROR, "unrecognized strategy number: %d", sk->sk_strategy);
@@ -443,12 +492,22 @@ spg_quad_leaf_consistent(PG_FUNCTION_ARGS)
 				break;
 			case RTContainedByStrategyNumber:
 
-				/*
-				 * For this operator, the query is a box not a point.  We
-				 * cheat to the extent of assuming that DatumGetPointP won't
-				 * do anything that would be bad for a pointer-to-box.
-				 */
-				res = SPTEST(box_contain_pt, query, datum);
+				switch (sk->sk_subtype) {
+				case BOXOID:
+					/*
+					 * For this operator, the query is a box not a point.  We
+					 * cheat to the extent of assuming that DatumGetPointP won't
+					 * do anything that would be bad for a pointer-to-box.
+					 */
+					res = SPTEST(box_contain_pt, query, datum);
+					break;
+				case CIRCLEOID:
+					res = SPTEST(circle_contain_pt, query, datum);
+					break;
+				default:
+					elog(ERROR, "unrecognized right type OID: %d", sk->sk_subtype);
+					break;
+				}
 				break;
 			default:
 				elog(ERROR, "unrecognized strategy number: %d", sk->sk_strategy);
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index cf63eb7d54..6ea7e21aa1 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1373,6 +1373,9 @@
   amoprighttype => 'box', amopstrategy => '8', amopopr => '<@(point,box)',
   amopmethod => 'spgist' },
 { amopfamily => 'spgist/quad_point_ops', amoplefttype => 'point',
+  amoprighttype => 'circle', amopstrategy => '8', amopopr => '<@(point,circle)',
+  amopmethod => 'spgist' },
+{ amopfamily => 'spgist/quad_point_ops', amoplefttype => 'point',
   amoprighttype => 'point', amopstrategy => '15', amoppurpose => 'o',
   amopopr => '<->(point,point)', amopmethod => 'spgist',
   amopsortfamily => 'btree/float_ops' },
diff --git a/src/test/regress/expected/create_index_spgist.out b/src/test/regress/expected/create_index_spgist.out
index 81b4a67c39..191e3088af 100644
--- a/src/test/regress/expected/create_index_spgist.out
+++ b/src/test/regress/expected/create_index_spgist.out
@@ -50,6 +50,102 @@ SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
   1057
 (1 row)
 
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(332,399),1.41>';
+ count 
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(332,399),1.42>';
+ count 
+-------
+  1000
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(332,401),1.41>';
+ count 
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(332,401),1.42>';
+ count 
+-------
+  1000
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(334,399),1.41>';
+ count 
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(334,399),1.42>';
+ count 
+-------
+  1000
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(334,401),1.41>';
+ count 
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(334,401),1.42>';
+ count 
+-------
+  1000
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(333,399),0.99>';
+ count 
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(333,399),1.01>';
+ count 
+-------
+  1000
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(333,401),0.99>';
+ count 
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(333,401),1.01>';
+ count 
+-------
+  1000
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(332,400),0.99>';
+ count 
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(332,400),1.01>';
+ count 
+-------
+  1000
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(334,400),0.99>';
+ count 
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(334,400),1.01>';
+ count 
+-------
+  1000
+(1 row)
+
 SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
  count 
 -------
diff --git a/src/test/regress/sql/create_index_spgist.sql b/src/test/regress/sql/create_index_spgist.sql
index 8e6c453307..25881387c3 100644
--- a/src/test/regress/sql/create_index_spgist.sql
+++ b/src/test/regress/sql/create_index_spgist.sql
@@ -42,6 +42,38 @@ SELECT count(*) FROM quad_point_tbl WHERE p <@ box '(200,200,1000,1000)';
 
 SELECT count(*) FROM quad_point_tbl WHERE box '(200,200,1000,1000)' @> p;
 
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(332,399),1.41>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(332,399),1.42>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(332,401),1.41>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(332,401),1.42>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(334,399),1.41>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(334,399),1.42>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(334,401),1.41>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(334,401),1.42>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(333,399),0.99>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(333,399),1.01>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(333,401),0.99>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(333,401),1.01>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(332,400),0.99>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(332,400),1.01>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(334,400),0.99>';
+
+SELECT count(*) FROM quad_point_tbl WHERE p <@ circle '<(334,400),1.01>';
+
 SELECT count(*) FROM quad_point_tbl WHERE p << '(5000, 4000)';
 
 SELECT count(*) FROM quad_point_tbl WHERE p >> '(5000, 4000)';
-- 
2.13.7

From 971657194b7c9d2e797823ea3ed03c42d6d04a7a Mon Sep 17 00:00:00 2001
From: "Matwey V. Kornilov" <matwey.korni...@gmail.com>
Date: Thu, 31 Jan 2019 12:56:48 +0300
Subject: [PATCH v2 1/3] Introduce helper variable in spgquadtreeproc.c

Use shorter variable name instead of repeating in->scankeys[i] in
spg_quad_leaf_consistent() and spg_quad_inner_consistent().

Signed-off-by: Matwey V. Kornilov <matwey.korni...@gmail.com>
---
 src/backend/access/spgist/spgquadtreeproc.c | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/backend/access/spgist/spgquadtreeproc.c b/src/backend/access/spgist/spgquadtreeproc.c
index e50108e1ca..f2e980b758 100644
--- a/src/backend/access/spgist/spgquadtreeproc.c
+++ b/src/backend/access/spgist/spgquadtreeproc.c
@@ -300,10 +300,11 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS)
 
 	for (i = 0; i < in->nkeys; i++)
 	{
-		Point	   *query = DatumGetPointP(in->scankeys[i].sk_argument);
+		const ScanKey sk = in->scankeys + i;
+		Point	   *query = DatumGetPointP(sk->sk_argument);
 		BOX		   *boxQuery;
 
-		switch (in->scankeys[i].sk_strategy)
+		switch (sk->sk_strategy)
 		{
 			case RTLeftStrategyNumber:
 				if (SPTEST(point_right, centroid, query))
@@ -331,7 +332,7 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS)
 				 * cheat to the extent of assuming that DatumGetPointP won't
 				 * do anything that would be bad for a pointer-to-box.
 				 */
-				boxQuery = DatumGetBoxP(in->scankeys[i].sk_argument);
+				boxQuery = DatumGetBoxP(sk->sk_argument);
 
 				if (DatumGetBool(DirectFunctionCall2(box_contain_pt,
 													 PointerGetDatum(boxQuery),
@@ -358,8 +359,7 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS)
 				}
 				break;
 			default:
-				elog(ERROR, "unrecognized strategy number: %d",
-					 in->scankeys[i].sk_strategy);
+				elog(ERROR, "unrecognized strategy number: %d", sk->sk_strategy);
 				break;
 		}
 
@@ -421,9 +421,10 @@ spg_quad_leaf_consistent(PG_FUNCTION_ARGS)
 	res = true;
 	for (i = 0; i < in->nkeys; i++)
 	{
-		Point	   *query = DatumGetPointP(in->scankeys[i].sk_argument);
+		const ScanKey sk = in->scankeys + i;
+		Point	   *query = DatumGetPointP(sk->sk_argument);
 
-		switch (in->scankeys[i].sk_strategy)
+		switch (sk->sk_strategy)
 		{
 			case RTLeftStrategyNumber:
 				res = SPTEST(point_left, datum, query);
@@ -450,8 +451,7 @@ spg_quad_leaf_consistent(PG_FUNCTION_ARGS)
 				res = SPTEST(box_contain_pt, query, datum);
 				break;
 			default:
-				elog(ERROR, "unrecognized strategy number: %d",
-					 in->scankeys[i].sk_strategy);
+				elog(ERROR, "unrecognized strategy number: %d", sk->sk_strategy);
 				break;
 		}
 
-- 
2.13.7

Reply via email to