CUBE seems a bit confused about ORDER BY

Started by Tomas Vondraabout 8 years ago33 messages
#1Tomas Vondra
tomas.vondra@2ndquadrant.com

Hi,

I've noticed this suspicious behavior of "cube" data type with ORDER BY,
which I believe is a bug in the extension (or the GiST index support).
The following example comes directly from regression tests added by
33bd250f (so CC Teodor and Stas, who are mentioned in the commit).

This query should produce results with ordering "ascending by 2nd
coordinate or upper right corner". To make it clear, I've added the
"c~>4" expression to the query, otherwise it's right from the test.

test=# SELECT c~>4 "c~>4", * FROM test_cube ORDER BY c~>4 LIMIT 15;
c~>4 | c
------+---------------------------
50 | (30333, 50),(30273, 6)
75 | (43301, 75),(43227, 43)
142 | (19650, 142),(19630, 51)
160 | (2424, 160),(2424, 81)
171 | (3449, 171),(3354, 108)
155 | (18037, 155),(17941, 109)
208 | (28511, 208),(28479, 114)
217 | (19946, 217),(19941, 118)
191 | (16906, 191),(16816, 139)
187 | (759, 187),(662, 163)
266 | (22684, 266),(22656, 181)
255 | (24423, 255),(24360, 213)
249 | (45989, 249),(45910, 222)
377 | (11399, 377),(11360, 294)
389 | (12162, 389),(12103, 309)
(15 rows)

As you can see, it's not actually sorted by the c~>4 coordinate (but by
c~>2, which it the last number).

Moreover, disabling index scans fixes the ordering:

test=# set enable_indexscan = off;
SET
test=# SELECT c~>4, * FROM test_cube ORDER BY c~>4 LIMIT 15; --
ascending by 2nd coordinate or upper right corner
?column? | c
----------+---------------------------
50 | (30333, 50),(30273, 6)
75 | (43301, 75),(43227, 43)
142 | (19650, 142),(19630, 51)
155 | (18037, 155),(17941, 109)
160 | (2424, 160),(2424, 81)
171 | (3449, 171),(3354, 108)
187 | (759, 187),(662, 163)
191 | (16906, 191),(16816, 139)
208 | (28511, 208),(28479, 114)
217 | (19946, 217),(19941, 118)
249 | (45989, 249),(45910, 222)
255 | (24423, 255),(24360, 213)
266 | (22684, 266),(22656, 181)
367 | (31018, 367),(30946, 333)
377 | (11399, 377),(11360, 294)
(15 rows)

Seems like a bug somewhere in gist_cube_ops, I guess?

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#2Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Tomas Vondra (#1)
Re: CUBE seems a bit confused about ORDER BY

Hi!

On Fri, Oct 20, 2017 at 12:52 AM, Tomas Vondra <tomas.vondra@2ndquadrant.com

wrote:

I've noticed this suspicious behavior of "cube" data type with ORDER BY,
which I believe is a bug in the extension (or the GiST index support).
The following example comes directly from regression tests added by
33bd250f (so CC Teodor and Stas, who are mentioned in the commit).

This query should produce results with ordering "ascending by 2nd
coordinate or upper right corner". To make it clear, I've added the
"c~>4" expression to the query, otherwise it's right from the test.

test=# SELECT c~>4 "c~>4", * FROM test_cube ORDER BY c~>4 LIMIT 15;
c~>4 | c
------+---------------------------
50 | (30333, 50),(30273, 6)
75 | (43301, 75),(43227, 43)
142 | (19650, 142),(19630, 51)
160 | (2424, 160),(2424, 81)
171 | (3449, 171),(3354, 108)
155 | (18037, 155),(17941, 109)
208 | (28511, 208),(28479, 114)
217 | (19946, 217),(19941, 118)
191 | (16906, 191),(16816, 139)
187 | (759, 187),(662, 163)
266 | (22684, 266),(22656, 181)
255 | (24423, 255),(24360, 213)
249 | (45989, 249),(45910, 222)
377 | (11399, 377),(11360, 294)
389 | (12162, 389),(12103, 309)
(15 rows)

As you can see, it's not actually sorted by the c~>4 coordinate (but by
c~>2, which it the last number).

Moreover, disabling index scans fixes the ordering:

test=# set enable_indexscan = off;
SET
test=# SELECT c~>4, * FROM test_cube ORDER BY c~>4 LIMIT 15; --
ascending by 2nd coordinate or upper right corner
?column? | c
----------+---------------------------
50 | (30333, 50),(30273, 6)
75 | (43301, 75),(43227, 43)
142 | (19650, 142),(19630, 51)
155 | (18037, 155),(17941, 109)
160 | (2424, 160),(2424, 81)
171 | (3449, 171),(3354, 108)
187 | (759, 187),(662, 163)
191 | (16906, 191),(16816, 139)
208 | (28511, 208),(28479, 114)
217 | (19946, 217),(19941, 118)
249 | (45989, 249),(45910, 222)
255 | (24423, 255),(24360, 213)
266 | (22684, 266),(22656, 181)
367 | (31018, 367),(30946, 333)
377 | (11399, 377),(11360, 294)
(15 rows)

Seems like a bug somewhere in gist_cube_ops, I guess?

+1,
that definitely looks like a bug. Thank you for reporting!
I'll take a look on it in couple days.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#3Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#2)
Re: CUBE seems a bit confused about ORDER BY

On Fri, Oct 20, 2017 at 1:01 AM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:

On Fri, Oct 20, 2017 at 12:52 AM, Tomas Vondra <
tomas.vondra@2ndquadrant.com> wrote:

I've noticed this suspicious behavior of "cube" data type with ORDER BY,
which I believe is a bug in the extension (or the GiST index support).
The following example comes directly from regression tests added by
33bd250f (so CC Teodor and Stas, who are mentioned in the commit).

This query should produce results with ordering "ascending by 2nd
coordinate or upper right corner". To make it clear, I've added the
"c~>4" expression to the query, otherwise it's right from the test.

test=# SELECT c~>4 "c~>4", * FROM test_cube ORDER BY c~>4 LIMIT 15;
c~>4 | c
------+---------------------------
50 | (30333, 50),(30273, 6)
75 | (43301, 75),(43227, 43)
142 | (19650, 142),(19630, 51)
160 | (2424, 160),(2424, 81)
171 | (3449, 171),(3354, 108)
155 | (18037, 155),(17941, 109)
208 | (28511, 208),(28479, 114)
217 | (19946, 217),(19941, 118)
191 | (16906, 191),(16816, 139)
187 | (759, 187),(662, 163)
266 | (22684, 266),(22656, 181)
255 | (24423, 255),(24360, 213)
249 | (45989, 249),(45910, 222)
377 | (11399, 377),(11360, 294)
389 | (12162, 389),(12103, 309)
(15 rows)

As you can see, it's not actually sorted by the c~>4 coordinate (but by
c~>2, which it the last number).

Moreover, disabling index scans fixes the ordering:

test=# set enable_indexscan = off;
SET
test=# SELECT c~>4, * FROM test_cube ORDER BY c~>4 LIMIT 15; --
ascending by 2nd coordinate or upper right corner
?column? | c
----------+---------------------------
50 | (30333, 50),(30273, 6)
75 | (43301, 75),(43227, 43)
142 | (19650, 142),(19630, 51)
155 | (18037, 155),(17941, 109)
160 | (2424, 160),(2424, 81)
171 | (3449, 171),(3354, 108)
187 | (759, 187),(662, 163)
191 | (16906, 191),(16816, 139)
208 | (28511, 208),(28479, 114)
217 | (19946, 217),(19941, 118)
249 | (45989, 249),(45910, 222)
255 | (24423, 255),(24360, 213)
266 | (22684, 266),(22656, 181)
367 | (31018, 367),(30946, 333)
377 | (11399, 377),(11360, 294)
(15 rows)

Seems like a bug somewhere in gist_cube_ops, I guess?

+1,
that definitely looks like a bug. Thank you for reporting!
I'll take a look on it in couple days.

I've reviewed code of ~> operator and its KNN-GiST support. Unfortunately,
it appears that it's broken in design... The explanation is above.

We've following implementation of ~> operator.

if (coord <= DIM(cube))

{
if (IS_POINT(cube))
PG_RETURN_FLOAT8(cube->x[coord - 1]);
else
PG_RETURN_FLOAT8(Min(cube->x[coord - 1],
cube->x[coord - 1 + DIM(cube)]));
}
else
{
if (IS_POINT(cube))
PG_RETURN_FLOAT8(cube->x[(coord - 1) % DIM(cube)]);
else
PG_RETURN_FLOAT8(Max(cube->x[coord - 1],
cube->x[coord - 1 - DIM(cube)]));
}

Thus, for cube of N dimensions [1; N - 1] are lower bounds and [N; 2*N - 1]
are upper bounds. However, we might have indexed cubes of different
dimensions. For example, for cube of 2 dimensions "cube ~> 3" selects
upper bound of 1st dimension. For cube of 3 dimensions "cube ~> 3" selects
lower bound of 3rd dimension.

Therefore, despite ~> operator was specially invented to be supported by
KNN-GIST, it can't serve this way.

Regarding particular case discovered by Tomas, I think the error is in the
GiST supporting code.

if (strategy == CubeKNNDistanceCoord)

{
int coord = PG_GETARG_INT32(1);
if (DIM(cube) == 0)
retval = 0.0;
else if (IS_POINT(cube))
retval = cube->x[(coord - 1) % DIM(cube)];
else
retval = Min(cube->x[(coord - 1) % DIM(cube)],
cube->x[(coord - 1) % DIM(cube) + DIM(cube)]);
}

g_cube_distance() always returns lower bound of cube. It should return
upper bound for coord > DIM(cube).

It would be also nice to provide descending ordering using KNN-GiST. It
would be especially effective for upper bound. Since, KNN-GiST doesn't
support explicit descending ordering, it might be useful to make ~>
operator return negative of coordinate when negative argument is provided.
For instance, '(1,2), (3,4)'::cube ~> -1 return -1.

I'd like to propose following way to resolve design issue. cube ~> (2*N -
1) should return lower bound of Nth coordinate of the cube while cube ~>
2*N should return upper bound of Nth coordinate. Then it would be
independent on number of coordinates in particular cube. For sure, it
would be user-visible incompatible change. But I think there is not so
many users of this operator yet. Also, current behavior of ~> seems quite
useless.

Any thoughts?

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#4Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#3)
1 attachment(s)
Re: CUBE seems a bit confused about ORDER BY

On Sun, Oct 29, 2017 at 1:30 AM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:

On Fri, Oct 20, 2017 at 1:01 AM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:
I've reviewed code of ~> operator and its KNN-GiST support.
Unfortunately, it appears that it's broken in design... The explanation is
above.

We've following implementation of ~> operator.

if (coord <= DIM(cube))

{
if (IS_POINT(cube))
PG_RETURN_FLOAT8(cube->x[coord - 1]);
else
PG_RETURN_FLOAT8(Min(cube->x[coord - 1],
cube->x[coord - 1 + DIM(cube)]));
}
else
{
if (IS_POINT(cube))
PG_RETURN_FLOAT8(cube->x[(coord - 1) % DIM(cube)]);
else
PG_RETURN_FLOAT8(Max(cube->x[coord - 1],
cube->x[coord - 1 - DIM(cube)]));
}

Thus, for cube of N dimensions [1; N - 1] are lower bounds and [N; 2*N -
1] are upper bounds. However, we might have indexed cubes of different
dimensions. For example, for cube of 2 dimensions "cube ~> 3" selects
upper bound of 1st dimension. For cube of 3 dimensions "cube ~> 3" selects
lower bound of 3rd dimension.

Therefore, despite ~> operator was specially invented to be supported by
KNN-GIST, it can't serve this way.

Regarding particular case discovered by Tomas, I think the error is in the
GiST supporting code.

if (strategy == CubeKNNDistanceCoord)

{
int coord = PG_GETARG_INT32(1);
if (DIM(cube) == 0)
retval = 0.0;
else if (IS_POINT(cube))
retval = cube->x[(coord - 1) % DIM(cube)];
else
retval = Min(cube->x[(coord - 1) % DIM(cube)],
cube->x[(coord - 1) % DIM(cube) + DIM(cube)]);
}

g_cube_distance() always returns lower bound of cube. It should return
upper bound for coord > DIM(cube).

It would be also nice to provide descending ordering using KNN-GiST. It
would be especially effective for upper bound. Since, KNN-GiST doesn't
support explicit descending ordering, it might be useful to make ~>
operator return negative of coordinate when negative argument is provided.
For instance, '(1,2), (3,4)'::cube ~> -1 return -1.

I'd like to propose following way to resolve design issue. cube ~> (2*N -
1) should return lower bound of Nth coordinate of the cube while cube ~>
2*N should return upper bound of Nth coordinate. Then it would be
independent on number of coordinates in particular cube. For sure, it
would be user-visible incompatible change. But I think there is not so
many users of this operator yet. Also, current behavior of ~> seems quite
useless.

Thus, I heard no objection to my approach. Attached patch changes ~>
operator in the proposed way and fixes KNN-GiST search for that. I'm going
to register this patch to the nearest commitfest.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

cube-knn-fix-1.patchapplication/octet-stream; name=cube-knn-fix-1.patchDownload
diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
new file mode 100644
index b770271..d2d67d9
*** a/contrib/cube/cube.c
--- b/contrib/cube/cube.c
*************** g_cube_distance(PG_FUNCTION_ARGS)
*** 1337,1351 ****
  
  	if (strategy == CubeKNNDistanceCoord)
  	{
  		int			coord = PG_GETARG_INT32(1);
  
! 		if (DIM(cube) == 0)
! 			retval = 0.0;
! 		else if (IS_POINT(cube))
! 			retval = cube->x[(coord - 1) % DIM(cube)];
  		else
! 			retval = Min(cube->x[(coord - 1) % DIM(cube)],
! 						 cube->x[(coord - 1) % DIM(cube) + DIM(cube)]);
  	}
  	else
  	{
--- 1337,1402 ----
  
  	if (strategy == CubeKNNDistanceCoord)
  	{
+ 		/*
+ 		 * Handle ordering by ~> operator.  See comments of cube_coord_llur()
+ 		 * for details
+ 		 */
  		int			coord = PG_GETARG_INT32(1);
+ 		bool		isLeaf = GistPageIsLeaf(entry->page);
+ 		bool		inverse = false;
  
! 		/* Return inversed value for negative coordinate */
! 		if (coord < 0)
! 		{
! 			coord = -coord;
! 			inverse = true;
! 		}
! 
! 		if (coord <= 2 * DIM(cube))
! 		{
! 			/* dimension index */
! 			int		index = (coord - 1) / 2;
! 			/* whether this is upper bound (lower bound otherwise) */
! 			bool	upper = ((coord - 1) % 2 == 1);
! 
! 			if (IS_POINT(cube))
! 			{
! 				retval = cube->x[index];
! 			}
! 			else
! 			{
! 				if (isLeaf)
! 				{
! 					/* For leaf just return required upper/lower bound */
! 					if (upper)
! 						retval = Max(cube->x[index], cube->x[index + DIM(cube)]);
! 					else
! 						retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
! 				}
! 				else
! 				{
! 					/*
! 					 * For non-leaf we should always return lower bound,
! 					 * because even upper bound of a child in the subtree can
! 					 * be as small as our lower bound.  For inversed case we
! 					 * return upper bound because it becomes lower bound for
! 					 * inversed value.
! 					 */
! 					if (!inverse)
! 						retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
! 					else
! 						retval = Max(cube->x[index], cube->x[index + DIM(cube)]);
! 				}
! 			}
! 		}
  		else
! 		{
! 			retval = 0.0;
! 		}
! 
! 		/* Inverse return value if needed */
! 		if (inverse)
! 			retval = -retval;
  	}
  	else
  	{
*************** cube_coord(PG_FUNCTION_ARGS)
*** 1492,1534 ****
  }
  
  
! /*
!  * This function works like cube_coord(),
!  * but rearranges coordinates of corners to get cube representation
!  * in the form of (lower left, upper right).
!  * For historical reasons that extension allows us to create cubes in form
!  * ((2,1),(1,2)) and instead of normalizing such cube to ((1,1),(2,2)) it
!  * stores cube in original way. But to get cubes ordered by one of dimensions
!  * directly from the index without extra sort step we need some
!  * representation-independent coordinate getter. This function implements it.
   */
  Datum
  cube_coord_llur(PG_FUNCTION_ARGS)
  {
  	NDBOX	   *cube = PG_GETARG_NDBOX_P(0);
  	int			coord = PG_GETARG_INT32(1);
  
! 	if (coord <= 0 || coord > 2 * DIM(cube))
  		ereport(ERROR,
  				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
! 				 errmsg("cube index %d is out of bounds", coord)));
  
! 	if (coord <= DIM(cube))
  	{
  		if (IS_POINT(cube))
! 			PG_RETURN_FLOAT8(cube->x[coord - 1]);
  		else
! 			PG_RETURN_FLOAT8(Min(cube->x[coord - 1],
! 								 cube->x[coord - 1 + DIM(cube)]));
  	}
  	else
  	{
! 		if (IS_POINT(cube))
! 			PG_RETURN_FLOAT8(cube->x[(coord - 1) % DIM(cube)]);
! 		else
! 			PG_RETURN_FLOAT8(Max(cube->x[coord - 1],
! 								 cube->x[coord - 1 - DIM(cube)]));
  	}
  }
  
  /* Increase or decrease box size by a radius in at least n dimensions. */
--- 1543,1625 ----
  }
  
  
! /*----
!  * This function works like cube_coord(), but rearranges coordinates in the
!  * way suitable to support coordinate ordering using KNN-GiST.  For historical
!  * reasons this extension allows us to create cubes in form ((2,1),(1,2)) and
!  * instead of normalizing such cube to ((1,1),(2,2)) it stores cube in original
!  * way.  But in order to get cubes ordered by one of dimensions from the index
!  * without explicit sort step we need this representation-independent coordinate
!  * getter.  Moreover, indexed dataset may contain cubes of different dimensions
!  * number.  Accordingly, this corrdinate getter should be able to return
!  * lower/upper bound for particular dimension independently on number of cube
!  * dimenstions.  Also, KNN-GiST supports only ascending sorting.  In order to
!  * support descinding sorting, this function returns inverse of value when
!  * negative coordinate is given.
!  *
!  * Long stoty short, this function uses following meaning of coordinates:
!  * # (2 * N - 1) -- lower bound of Nth dimension,
!  * # (2 * N) -- upper bound of Nth dimenstion,
!  * # - (2 * N - 1) -- negative of lower bound of Nth dimension,
!  * # - (2 * N) -- negative of upper bound of Nth dimenstion.
!  *
!  * When given coordinate exceeds number of cube dimensions, then 0 returned
!  * (reproducing logic of GiST indexing of variable-length cubes).
   */
  Datum
  cube_coord_llur(PG_FUNCTION_ARGS)
  {
  	NDBOX	   *cube = PG_GETARG_NDBOX_P(0);
  	int			coord = PG_GETARG_INT32(1);
+ 	bool		inverse = false;
+ 	float8		result;
  
! 	/* 0 is the only unsupported coordinate value */
! 	if (coord == 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
! 				 errmsg("zero cube index is not defined")));
  
! 	/* Return inversed value for negative coordinate */
! 	if (coord < 0)
  	{
+ 		coord = -coord;
+ 		inverse = true;
+ 	}
+ 
+ 	if (coord <= 2 * DIM(cube))
+ 	{
+ 		/* dimension index */
+ 		int		index = (coord - 1) / 2;
+ 		/* whether this is upper bound (lower bound otherwise) */
+ 		bool	upper = ((coord - 1) % 2 == 1);
+ 
  		if (IS_POINT(cube))
! 		{
! 			result = cube->x[index];
! 		}
  		else
! 		{
! 			if (upper)
! 				result = Max(cube->x[index], cube->x[index + DIM(cube)]);
! 			else
! 				result = Min(cube->x[index], cube->x[index + DIM(cube)]);
! 		}
  	}
  	else
  	{
! 		/*
! 		 * Return zero if coordinate is out of bound.  That reproduces logic of
! 		 * how cubes with low dimension number are expanded duing GiST indexing.
! 		 */
! 		result = 0.0;
  	}
+ 
+ 	/* Inverse value if needed */
+ 	if (inverse)
+ 		result = -result;
+ 
+ 	PG_RETURN_FLOAT8(result);
  }
  
  /* Increase or decrease box size by a radius in at least n dimensions. */
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
new file mode 100644
index 328b3b5..b6a694e
*** a/contrib/cube/expected/cube.out
--- b/contrib/cube/expected/cube.out
*************** SELECT cube(array[40,50,60], array[10,20
*** 1532,1568 ****
  SELECT cube(array[10,20,30], array[40,50,60])~>2;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>2;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[10,20,30], array[40,50,60])~>3;
   ?column? 
  ----------
!        30
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>3;
   ?column? 
  ----------
!        30
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>0;
! ERROR:  cube index 0 is out of bounds
  SELECT cube(array[40,50,60], array[10,20,30])~>4;
   ?column? 
  ----------
!        40
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
! ERROR:  cube index -1 is out of bounds
  -- Load some example data and build the index
  --
  CREATE TABLE test_cube (c cube);
--- 1532,1572 ----
  SELECT cube(array[10,20,30], array[40,50,60])~>2;
   ?column? 
  ----------
!        40
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>2;
   ?column? 
  ----------
!        40
  (1 row)
  
  SELECT cube(array[10,20,30], array[40,50,60])~>3;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>3;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>0;
! ERROR:  zero cube index is not defined
  SELECT cube(array[40,50,60], array[10,20,30])~>4;
   ?column? 
  ----------
!        50
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
!  ?column? 
! ----------
!       -10
! (1 row)
! 
  -- Load some example data and build the index
  --
  CREATE TABLE test_cube (c cube);
*************** SELECT * FROM test_cube WHERE c && '(300
*** 1589,1747 ****
   (2424, 160),(2424, 81)
  (5 rows)
  
! -- kNN with index
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            |       dist       
  -------------------------+------------------
   (337, 455),(240, 359)   |                0
   (759, 187),(662, 163)   |              162
   (948, 1201),(907, 1156) | 772.000647668122
   (1444, 403),(1346, 344) |              846
-  (369, 1457),(278, 1409) |              909
  (5 rows)
  
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
   (948, 1201),(907, 1156) |  656
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
  (5 rows)
  
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
-  (948, 1201),(907, 1156) | 1063
  (5 rows)
  
! -- kNN-based sorting
! SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
!              c             
! ---------------------------
!  (54, 38679),(3, 38602)
!  (83, 10271),(15, 10265)
!  (122, 46832),(64, 46762)
!  (167, 17214),(92, 17184)
!  (161, 24465),(107, 24374)
!  (162, 26040),(120, 25963)
!  (154, 4019),(138, 3990)
!  (259, 1850),(175, 1820)
!  (207, 40886),(179, 40879)
!  (288, 49588),(204, 49571)
!  (270, 32616),(226, 32607)
!  (318, 31489),(235, 31404)
!  (337, 455),(240, 359)
!  (270, 29508),(264, 29440)
!  (369, 1457),(278, 1409)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
!              c             
! ---------------------------
!  (30333, 50),(30273, 6)
!  (43301, 75),(43227, 43)
!  (19650, 142),(19630, 51)
!  (2424, 160),(2424, 81)
!  (3449, 171),(3354, 108)
!  (18037, 155),(17941, 109)
!  (28511, 208),(28479, 114)
!  (19946, 217),(19941, 118)
!  (16906, 191),(16816, 139)
!  (759, 187),(662, 163)
!  (22684, 266),(22656, 181)
!  (24423, 255),(24360, 213)
!  (45989, 249),(45910, 222)
!  (11399, 377),(11360, 294)
!  (12162, 389),(12103, 309)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
!                c               
! -------------------------------
!  (50027, 49230),(49951, 49214)
!  (49980, 35004),(49937, 34963)
!  (49985, 6436),(49927, 6338)
!  (49999, 27218),(49908, 27176)
!  (49954, 1340),(49905, 1294)
!  (49944, 25163),(49902, 25153)
!  (49981, 34876),(49898, 34786)
!  (49957, 43390),(49897, 43384)
!  (49853, 18504),(49848, 18503)
!  (49902, 41752),(49818, 41746)
!  (49907, 30225),(49810, 30158)
!  (49843, 5175),(49808, 5145)
!  (49887, 24274),(49805, 24184)
!  (49847, 7128),(49798, 7067)
!  (49820, 7990),(49771, 7967)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
!                c               
! -------------------------------
!  (36311, 50073),(36258, 49987)
!  (30746, 50040),(30727, 49992)
!  (2168, 50012),(2108, 49914)
!  (21551, 49983),(21492, 49885)
!  (17954, 49975),(17865, 49915)
!  (3531, 49962),(3463, 49934)
!  (19128, 49932),(19112, 49849)
!  (31287, 49923),(31236, 49913)
!  (43925, 49912),(43888, 49878)
!  (29261, 49910),(29247, 49818)
!  (14913, 49873),(14849, 49836)
!  (20007, 49858),(19921, 49778)
!  (38266, 49852),(38233, 49844)
!  (37595, 49849),(37581, 49834)
!  (46151, 49848),(46058, 49830)
  (15 rows)
  
! -- same thing for index with points
! CREATE TABLE test_point(c cube);
! INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
! CREATE INDEX ON test_point USING gist(c);
! SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
!             c             
! --------------------------
!  (54, 38679, 3, 38602)
!  (83, 10271, 15, 10265)
!  (122, 46832, 64, 46762)
!  (154, 4019, 138, 3990)
!  (161, 24465, 107, 24374)
!  (162, 26040, 120, 25963)
!  (167, 17214, 92, 17184)
!  (207, 40886, 179, 40879)
!  (259, 1850, 175, 1820)
!  (270, 29508, 264, 29440)
!  (270, 32616, 226, 32607)
!  (288, 49588, 204, 49571)
!  (318, 31489, 235, 31404)
!  (326, 18837, 285, 18817)
!  (337, 455, 240, 359)
  (15 rows)
  
! SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
!               c               
! ------------------------------
!  (30746, 50040, 30727, 49992)
!  (36311, 50073, 36258, 49987)
!  (3531, 49962, 3463, 49934)
!  (17954, 49975, 17865, 49915)
!  (2168, 50012, 2108, 49914)
!  (31287, 49923, 31236, 49913)
!  (21551, 49983, 21492, 49885)
!  (43925, 49912, 43888, 49878)
!  (19128, 49932, 19112, 49849)
!  (38266, 49852, 38233, 49844)
!  (14913, 49873, 14849, 49836)
!  (37595, 49849, 37581, 49834)
!  (46151, 49848, 46058, 49830)
!  (29261, 49910, 29247, 49818)
!  (19233, 49824, 19185, 49794)
  (15 rows)
  
--- 1593,1984 ----
   (2424, 160),(2424, 81)
  (5 rows)
  
! -- Test kNN
! INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
! SET enable_seqscan = OFF;
! SET enable_indexscan = ON;
! -- Test different metrics
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            |       dist       
  -------------------------+------------------
   (337, 455),(240, 359)   |                0
+  (1, 1)                  | 140.007142674936
   (759, 187),(662, 163)   |              162
   (948, 1201),(907, 1156) | 772.000647668122
   (1444, 403),(1346, 344) |              846
  (5 rows)
  
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
+  (1, 1)                  |   99
   (759, 187),(662, 163)   |  162
   (948, 1201),(907, 1156) |  656
   (1444, 403),(1346, 344) |  846
+ (5 rows)
+ 
+ SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
+             c            | dist 
+ -------------------------+------
+  (337, 455),(240, 359)   |    0
+  (759, 187),(662, 163)   |  162
+  (1, 1)                  |  198
+  (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
  (5 rows)
  
+ -- Test sorting by coordinates
+ SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (0, 100000)
+         1 | (1, 1)
+         3 | (54, 38679),(3, 38602)
+        15 | (83, 10271),(15, 10265)
+        64 | (122, 46832),(64, 46762)
+        92 | (167, 17214),(92, 17184)
+       107 | (161, 24465),(107, 24374)
+       120 | (162, 26040),(120, 25963)
+       138 | (154, 4019),(138, 3990)
+       175 | (259, 1850),(175, 1820)
+       179 | (207, 40886),(179, 40879)
+       204 | (288, 49588),(204, 49571)
+       226 | (270, 32616),(226, 32607)
+       235 | (318, 31489),(235, 31404)
+       240 | (337, 455),(240, 359)
+ (15 rows)
+ 
+ SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (0, 100000)
+         1 | (1, 1)
+        54 | (54, 38679),(3, 38602)
+        83 | (83, 10271),(15, 10265)
+       122 | (122, 46832),(64, 46762)
+       154 | (154, 4019),(138, 3990)
+       161 | (161, 24465),(107, 24374)
+       162 | (162, 26040),(120, 25963)
+       167 | (167, 17214),(92, 17184)
+       207 | (207, 40886),(179, 40879)
+       259 | (259, 1850),(175, 1820)
+       270 | (270, 29508),(264, 29440)
+       270 | (270, 32616),(226, 32607)
+       288 | (288, 49588),(204, 49571)
+       318 | (318, 31489),(235, 31404)
+ (15 rows)
+ 
+ SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (100000)
+         1 | (1, 1)
+         6 | (30333, 50),(30273, 6)
+        43 | (43301, 75),(43227, 43)
+        51 | (19650, 142),(19630, 51)
+        81 | (2424, 160),(2424, 81)
+       108 | (3449, 171),(3354, 108)
+       109 | (18037, 155),(17941, 109)
+       114 | (28511, 208),(28479, 114)
+       118 | (19946, 217),(19941, 118)
+       139 | (16906, 191),(16816, 139)
+       163 | (759, 187),(662, 163)
+       181 | (22684, 266),(22656, 181)
+       213 | (24423, 255),(24360, 213)
+       222 | (45989, 249),(45910, 222)
+ (15 rows)
+ 
+ SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (100000)
+         1 | (1, 1)
+        50 | (30333, 50),(30273, 6)
+        75 | (43301, 75),(43227, 43)
+       142 | (19650, 142),(19630, 51)
+       155 | (18037, 155),(17941, 109)
+       160 | (2424, 160),(2424, 81)
+       171 | (3449, 171),(3354, 108)
+       187 | (759, 187),(662, 163)
+       191 | (16906, 191),(16816, 139)
+       208 | (28511, 208),(28479, 114)
+       217 | (19946, 217),(19941, 118)
+       249 | (45989, 249),(45910, 222)
+       255 | (24423, 255),(24360, 213)
+       266 | (22684, 266),(22656, 181)
+ (15 rows)
+ 
+ SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -49951 | (50027, 49230),(49951, 49214)
+    -49937 | (49980, 35004),(49937, 34963)
+    -49927 | (49985, 6436),(49927, 6338)
+    -49908 | (49999, 27218),(49908, 27176)
+    -49905 | (49954, 1340),(49905, 1294)
+    -49902 | (49944, 25163),(49902, 25153)
+    -49898 | (49981, 34876),(49898, 34786)
+    -49897 | (49957, 43390),(49897, 43384)
+    -49848 | (49853, 18504),(49848, 18503)
+    -49818 | (49902, 41752),(49818, 41746)
+    -49810 | (49907, 30225),(49810, 30158)
+    -49808 | (49843, 5175),(49808, 5145)
+    -49805 | (49887, 24274),(49805, 24184)
+    -49798 | (49847, 7128),(49798, 7067)
+ (15 rows)
+ 
+ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -50027 | (50027, 49230),(49951, 49214)
+    -49999 | (49999, 27218),(49908, 27176)
+    -49985 | (49985, 6436),(49927, 6338)
+    -49981 | (49981, 34876),(49898, 34786)
+    -49980 | (49980, 35004),(49937, 34963)
+    -49957 | (49957, 43390),(49897, 43384)
+    -49954 | (49954, 1340),(49905, 1294)
+    -49944 | (49944, 25163),(49902, 25153)
+    -49907 | (49907, 30225),(49810, 30158)
+    -49902 | (49902, 41752),(49818, 41746)
+    -49887 | (49887, 24274),(49805, 24184)
+    -49853 | (49853, 18504),(49848, 18503)
+    -49847 | (49847, 7128),(49798, 7067)
+    -49843 | (49843, 5175),(49808, 5145)
+ (15 rows)
+ 
+ SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -49992 | (30746, 50040),(30727, 49992)
+    -49987 | (36311, 50073),(36258, 49987)
+    -49934 | (3531, 49962),(3463, 49934)
+    -49915 | (17954, 49975),(17865, 49915)
+    -49914 | (2168, 50012),(2108, 49914)
+    -49913 | (31287, 49923),(31236, 49913)
+    -49885 | (21551, 49983),(21492, 49885)
+    -49878 | (43925, 49912),(43888, 49878)
+    -49849 | (19128, 49932),(19112, 49849)
+    -49844 | (38266, 49852),(38233, 49844)
+    -49836 | (14913, 49873),(14849, 49836)
+    -49834 | (37595, 49849),(37581, 49834)
+    -49830 | (46151, 49848),(46058, 49830)
+    -49818 | (29261, 49910),(29247, 49818)
+ (15 rows)
+ 
+ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -50073 | (36311, 50073),(36258, 49987)
+    -50040 | (30746, 50040),(30727, 49992)
+    -50012 | (2168, 50012),(2108, 49914)
+    -49983 | (21551, 49983),(21492, 49885)
+    -49975 | (17954, 49975),(17865, 49915)
+    -49962 | (3531, 49962),(3463, 49934)
+    -49932 | (19128, 49932),(19112, 49849)
+    -49923 | (31287, 49923),(31236, 49913)
+    -49912 | (43925, 49912),(43888, 49878)
+    -49910 | (29261, 49910),(29247, 49818)
+    -49873 | (14913, 49873),(14849, 49836)
+    -49858 | (20007, 49858),(19921, 49778)
+    -49852 | (38266, 49852),(38233, 49844)
+    -49849 | (37595, 49849),(37581, 49834)
+ (15 rows)
+ 
+ -- Same queries with sequential scan (should give the same results as above)
+ SET enable_seqscan = ON;
+ SET enable_indexscan = OFF;
+ SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
+             c            |       dist       
+ -------------------------+------------------
+  (337, 455),(240, 359)   |                0
+  (1, 1)                  | 140.007142674936
+  (759, 187),(662, 163)   |              162
+  (948, 1201),(907, 1156) | 772.000647668122
+  (1444, 403),(1346, 344) |              846
+ (5 rows)
+ 
+ SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
+             c            | dist 
+ -------------------------+------
+  (337, 455),(240, 359)   |    0
+  (1, 1)                  |   99
+  (759, 187),(662, 163)   |  162
+  (948, 1201),(907, 1156) |  656
+  (1444, 403),(1346, 344) |  846
+ (5 rows)
+ 
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
+  (1, 1)                  |  198
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
  (5 rows)
  
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!         3 | (54, 38679),(3, 38602)
!        15 | (83, 10271),(15, 10265)
!        64 | (122, 46832),(64, 46762)
!        92 | (167, 17214),(92, 17184)
!       107 | (161, 24465),(107, 24374)
!       120 | (162, 26040),(120, 25963)
!       138 | (154, 4019),(138, 3990)
!       175 | (259, 1850),(175, 1820)
!       179 | (207, 40886),(179, 40879)
!       204 | (288, 49588),(204, 49571)
!       226 | (270, 32616),(226, 32607)
!       235 | (318, 31489),(235, 31404)
!       240 | (337, 455),(240, 359)
  (15 rows)
  
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!        54 | (54, 38679),(3, 38602)
!        83 | (83, 10271),(15, 10265)
!       122 | (122, 46832),(64, 46762)
!       154 | (154, 4019),(138, 3990)
!       161 | (161, 24465),(107, 24374)
!       162 | (162, 26040),(120, 25963)
!       167 | (167, 17214),(92, 17184)
!       207 | (207, 40886),(179, 40879)
!       259 | (259, 1850),(175, 1820)
!       270 | (270, 29508),(264, 29440)
!       270 | (270, 32616),(226, 32607)
!       288 | (288, 49588),(204, 49571)
!       318 | (318, 31489),(235, 31404)
  (15 rows)
  
! SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (100000)
!         1 | (1, 1)
!         6 | (30333, 50),(30273, 6)
!        43 | (43301, 75),(43227, 43)
!        51 | (19650, 142),(19630, 51)
!        81 | (2424, 160),(2424, 81)
!       108 | (3449, 171),(3354, 108)
!       109 | (18037, 155),(17941, 109)
!       114 | (28511, 208),(28479, 114)
!       118 | (19946, 217),(19941, 118)
!       139 | (16906, 191),(16816, 139)
!       163 | (759, 187),(662, 163)
!       181 | (22684, 266),(22656, 181)
!       213 | (24423, 255),(24360, 213)
!       222 | (45989, 249),(45910, 222)
  (15 rows)
  
! SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (100000)
!         1 | (1, 1)
!        50 | (30333, 50),(30273, 6)
!        75 | (43301, 75),(43227, 43)
!       142 | (19650, 142),(19630, 51)
!       155 | (18037, 155),(17941, 109)
!       160 | (2424, 160),(2424, 81)
!       171 | (3449, 171),(3354, 108)
!       187 | (759, 187),(662, 163)
!       191 | (16906, 191),(16816, 139)
!       208 | (28511, 208),(28479, 114)
!       217 | (19946, 217),(19941, 118)
!       249 | (45989, 249),(45910, 222)
!       255 | (24423, 255),(24360, 213)
!       266 | (22684, 266),(22656, 181)
  (15 rows)
  
! SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
!  ?column? |               c               
! ----------+-------------------------------
!   -100000 | (100000)
!    -49951 | (50027, 49230),(49951, 49214)
!    -49937 | (49980, 35004),(49937, 34963)
!    -49927 | (49985, 6436),(49927, 6338)
!    -49908 | (49999, 27218),(49908, 27176)
!    -49905 | (49954, 1340),(49905, 1294)
!    -49902 | (49944, 25163),(49902, 25153)
!    -49898 | (49981, 34876),(49898, 34786)
!    -49897 | (49957, 43390),(49897, 43384)
!    -49848 | (49853, 18504),(49848, 18503)
!    -49818 | (49902, 41752),(49818, 41746)
!    -49810 | (49907, 30225),(49810, 30158)
!    -49808 | (49843, 5175),(49808, 5145)
!    -49805 | (49887, 24274),(49805, 24184)
!    -49798 | (49847, 7128),(49798, 7067)
  (15 rows)
  
! SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
!  ?column? |               c               
! ----------+-------------------------------
!   -100000 | (100000)
!    -50027 | (50027, 49230),(49951, 49214)
!    -49999 | (49999, 27218),(49908, 27176)
!    -49985 | (49985, 6436),(49927, 6338)
!    -49981 | (49981, 34876),(49898, 34786)
!    -49980 | (49980, 35004),(49937, 34963)
!    -49957 | (49957, 43390),(49897, 43384)
!    -49954 | (49954, 1340),(49905, 1294)
!    -49944 | (49944, 25163),(49902, 25153)
!    -49907 | (49907, 30225),(49810, 30158)
!    -49902 | (49902, 41752),(49818, 41746)
!    -49887 | (49887, 24274),(49805, 24184)
!    -49853 | (49853, 18504),(49848, 18503)
!    -49847 | (49847, 7128),(49798, 7067)
!    -49843 | (49843, 5175),(49808, 5145)
! (15 rows)
! 
! SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
!  ?column? |               c               
! ----------+-------------------------------
!   -100000 | (0, 100000)
!    -49992 | (30746, 50040),(30727, 49992)
!    -49987 | (36311, 50073),(36258, 49987)
!    -49934 | (3531, 49962),(3463, 49934)
!    -49915 | (17954, 49975),(17865, 49915)
!    -49914 | (2168, 50012),(2108, 49914)
!    -49913 | (31287, 49923),(31236, 49913)
!    -49885 | (21551, 49983),(21492, 49885)
!    -49878 | (43925, 49912),(43888, 49878)
!    -49849 | (19128, 49932),(19112, 49849)
!    -49844 | (38266, 49852),(38233, 49844)
!    -49836 | (14913, 49873),(14849, 49836)
!    -49834 | (37595, 49849),(37581, 49834)
!    -49830 | (46151, 49848),(46058, 49830)
!    -49818 | (29261, 49910),(29247, 49818)
! (15 rows)
! 
! SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
!  ?column? |               c               
! ----------+-------------------------------
!   -100000 | (0, 100000)
!    -50073 | (36311, 50073),(36258, 49987)
!    -50040 | (30746, 50040),(30727, 49992)
!    -50012 | (2168, 50012),(2108, 49914)
!    -49983 | (21551, 49983),(21492, 49885)
!    -49975 | (17954, 49975),(17865, 49915)
!    -49962 | (3531, 49962),(3463, 49934)
!    -49932 | (19128, 49932),(19112, 49849)
!    -49923 | (31287, 49923),(31236, 49913)
!    -49912 | (43925, 49912),(43888, 49878)
!    -49910 | (29261, 49910),(29247, 49818)
!    -49873 | (14913, 49873),(14849, 49836)
!    -49858 | (20007, 49858),(19921, 49778)
!    -49852 | (38266, 49852),(38233, 49844)
!    -49849 | (37595, 49849),(37581, 49834)
  (15 rows)
  
diff --git a/contrib/cube/expected/cube_2.out b/contrib/cube/expected/cube_2.out
new file mode 100644
index 1aa5cf2..c6ce3a3
*** a/contrib/cube/expected/cube_2.out
--- b/contrib/cube/expected/cube_2.out
*************** SELECT * FROM test_cube WHERE c && '(300
*** 1589,1747 ****
   (2424, 160),(2424, 81)
  (5 rows)
  
! -- kNN with index
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            |       dist       
  -------------------------+------------------
   (337, 455),(240, 359)   |                0
   (759, 187),(662, 163)   |              162
   (948, 1201),(907, 1156) | 772.000647668122
   (1444, 403),(1346, 344) |              846
-  (369, 1457),(278, 1409) |              909
  (5 rows)
  
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
   (948, 1201),(907, 1156) |  656
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
  (5 rows)
  
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
-  (948, 1201),(907, 1156) | 1063
  (5 rows)
  
! -- kNN-based sorting
! SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
!              c             
! ---------------------------
!  (54, 38679),(3, 38602)
!  (83, 10271),(15, 10265)
!  (122, 46832),(64, 46762)
!  (167, 17214),(92, 17184)
!  (161, 24465),(107, 24374)
!  (162, 26040),(120, 25963)
!  (154, 4019),(138, 3990)
!  (259, 1850),(175, 1820)
!  (207, 40886),(179, 40879)
!  (288, 49588),(204, 49571)
!  (270, 32616),(226, 32607)
!  (318, 31489),(235, 31404)
!  (337, 455),(240, 359)
!  (270, 29508),(264, 29440)
!  (369, 1457),(278, 1409)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
!              c             
! ---------------------------
!  (30333, 50),(30273, 6)
!  (43301, 75),(43227, 43)
!  (19650, 142),(19630, 51)
!  (2424, 160),(2424, 81)
!  (3449, 171),(3354, 108)
!  (18037, 155),(17941, 109)
!  (28511, 208),(28479, 114)
!  (19946, 217),(19941, 118)
!  (16906, 191),(16816, 139)
!  (759, 187),(662, 163)
!  (22684, 266),(22656, 181)
!  (24423, 255),(24360, 213)
!  (45989, 249),(45910, 222)
!  (11399, 377),(11360, 294)
!  (12162, 389),(12103, 309)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
!                c               
! -------------------------------
!  (50027, 49230),(49951, 49214)
!  (49980, 35004),(49937, 34963)
!  (49985, 6436),(49927, 6338)
!  (49999, 27218),(49908, 27176)
!  (49954, 1340),(49905, 1294)
!  (49944, 25163),(49902, 25153)
!  (49981, 34876),(49898, 34786)
!  (49957, 43390),(49897, 43384)
!  (49853, 18504),(49848, 18503)
!  (49902, 41752),(49818, 41746)
!  (49907, 30225),(49810, 30158)
!  (49843, 5175),(49808, 5145)
!  (49887, 24274),(49805, 24184)
!  (49847, 7128),(49798, 7067)
!  (49820, 7990),(49771, 7967)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
!                c               
! -------------------------------
!  (36311, 50073),(36258, 49987)
!  (30746, 50040),(30727, 49992)
!  (2168, 50012),(2108, 49914)
!  (21551, 49983),(21492, 49885)
!  (17954, 49975),(17865, 49915)
!  (3531, 49962),(3463, 49934)
!  (19128, 49932),(19112, 49849)
!  (31287, 49923),(31236, 49913)
!  (43925, 49912),(43888, 49878)
!  (29261, 49910),(29247, 49818)
!  (14913, 49873),(14849, 49836)
!  (20007, 49858),(19921, 49778)
!  (38266, 49852),(38233, 49844)
!  (37595, 49849),(37581, 49834)
!  (46151, 49848),(46058, 49830)
  (15 rows)
  
! -- same thing for index with points
! CREATE TABLE test_point(c cube);
! INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
! CREATE INDEX ON test_point USING gist(c);
! SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
!             c             
! --------------------------
!  (54, 38679, 3, 38602)
!  (83, 10271, 15, 10265)
!  (122, 46832, 64, 46762)
!  (154, 4019, 138, 3990)
!  (161, 24465, 107, 24374)
!  (162, 26040, 120, 25963)
!  (167, 17214, 92, 17184)
!  (207, 40886, 179, 40879)
!  (259, 1850, 175, 1820)
!  (270, 29508, 264, 29440)
!  (270, 32616, 226, 32607)
!  (288, 49588, 204, 49571)
!  (318, 31489, 235, 31404)
!  (326, 18837, 285, 18817)
!  (337, 455, 240, 359)
  (15 rows)
  
! SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
!               c               
! ------------------------------
!  (30746, 50040, 30727, 49992)
!  (36311, 50073, 36258, 49987)
!  (3531, 49962, 3463, 49934)
!  (17954, 49975, 17865, 49915)
!  (2168, 50012, 2108, 49914)
!  (31287, 49923, 31236, 49913)
!  (21551, 49983, 21492, 49885)
!  (43925, 49912, 43888, 49878)
!  (19128, 49932, 19112, 49849)
!  (38266, 49852, 38233, 49844)
!  (14913, 49873, 14849, 49836)
!  (37595, 49849, 37581, 49834)
!  (46151, 49848, 46058, 49830)
!  (29261, 49910, 29247, 49818)
!  (19233, 49824, 19185, 49794)
  (15 rows)
  
--- 1589,1980 ----
   (2424, 160),(2424, 81)
  (5 rows)
  
! -- Test kNN
! INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
! SET enable_seqscan = OFF;
! SET enable_indexscan = ON;
! -- Test different metrics
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            |       dist       
  -------------------------+------------------
   (337, 455),(240, 359)   |                0
+  (1, 1)                  | 140.007142674936
   (759, 187),(662, 163)   |              162
   (948, 1201),(907, 1156) | 772.000647668122
   (1444, 403),(1346, 344) |              846
  (5 rows)
  
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
+  (1, 1)                  |   99
   (759, 187),(662, 163)   |  162
   (948, 1201),(907, 1156) |  656
   (1444, 403),(1346, 344) |  846
+ (5 rows)
+ 
+ SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
+             c            | dist 
+ -------------------------+------
+  (337, 455),(240, 359)   |    0
+  (759, 187),(662, 163)   |  162
+  (1, 1)                  |  198
+  (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
  (5 rows)
  
+ -- Test sorting by coordinates
+ SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (0, 100000)
+         1 | (1, 1)
+         3 | (54, 38679),(3, 38602)
+        15 | (83, 10271),(15, 10265)
+        64 | (122, 46832),(64, 46762)
+        92 | (167, 17214),(92, 17184)
+       107 | (161, 24465),(107, 24374)
+       120 | (162, 26040),(120, 25963)
+       138 | (154, 4019),(138, 3990)
+       175 | (259, 1850),(175, 1820)
+       179 | (207, 40886),(179, 40879)
+       204 | (288, 49588),(204, 49571)
+       226 | (270, 32616),(226, 32607)
+       235 | (318, 31489),(235, 31404)
+       240 | (337, 455),(240, 359)
+ (15 rows)
+ 
+ SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (0, 100000)
+         1 | (1, 1)
+        54 | (54, 38679),(3, 38602)
+        83 | (83, 10271),(15, 10265)
+       122 | (122, 46832),(64, 46762)
+       154 | (154, 4019),(138, 3990)
+       161 | (161, 24465),(107, 24374)
+       162 | (162, 26040),(120, 25963)
+       167 | (167, 17214),(92, 17184)
+       207 | (207, 40886),(179, 40879)
+       259 | (259, 1850),(175, 1820)
+       270 | (270, 29508),(264, 29440)
+       270 | (270, 32616),(226, 32607)
+       288 | (288, 49588),(204, 49571)
+       318 | (318, 31489),(235, 31404)
+ (15 rows)
+ 
+ SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (100000)
+         1 | (1, 1)
+         6 | (30333, 50),(30273, 6)
+        43 | (43301, 75),(43227, 43)
+        51 | (19650, 142),(19630, 51)
+        81 | (2424, 160),(2424, 81)
+       108 | (3449, 171),(3354, 108)
+       109 | (18037, 155),(17941, 109)
+       114 | (28511, 208),(28479, 114)
+       118 | (19946, 217),(19941, 118)
+       139 | (16906, 191),(16816, 139)
+       163 | (759, 187),(662, 163)
+       181 | (22684, 266),(22656, 181)
+       213 | (24423, 255),(24360, 213)
+       222 | (45989, 249),(45910, 222)
+ (15 rows)
+ 
+ SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (100000)
+         1 | (1, 1)
+        50 | (30333, 50),(30273, 6)
+        75 | (43301, 75),(43227, 43)
+       142 | (19650, 142),(19630, 51)
+       155 | (18037, 155),(17941, 109)
+       160 | (2424, 160),(2424, 81)
+       171 | (3449, 171),(3354, 108)
+       187 | (759, 187),(662, 163)
+       191 | (16906, 191),(16816, 139)
+       208 | (28511, 208),(28479, 114)
+       217 | (19946, 217),(19941, 118)
+       249 | (45989, 249),(45910, 222)
+       255 | (24423, 255),(24360, 213)
+       266 | (22684, 266),(22656, 181)
+ (15 rows)
+ 
+ SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -49951 | (50027, 49230),(49951, 49214)
+    -49937 | (49980, 35004),(49937, 34963)
+    -49927 | (49985, 6436),(49927, 6338)
+    -49908 | (49999, 27218),(49908, 27176)
+    -49905 | (49954, 1340),(49905, 1294)
+    -49902 | (49944, 25163),(49902, 25153)
+    -49898 | (49981, 34876),(49898, 34786)
+    -49897 | (49957, 43390),(49897, 43384)
+    -49848 | (49853, 18504),(49848, 18503)
+    -49818 | (49902, 41752),(49818, 41746)
+    -49810 | (49907, 30225),(49810, 30158)
+    -49808 | (49843, 5175),(49808, 5145)
+    -49805 | (49887, 24274),(49805, 24184)
+    -49798 | (49847, 7128),(49798, 7067)
+ (15 rows)
+ 
+ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -50027 | (50027, 49230),(49951, 49214)
+    -49999 | (49999, 27218),(49908, 27176)
+    -49985 | (49985, 6436),(49927, 6338)
+    -49981 | (49981, 34876),(49898, 34786)
+    -49980 | (49980, 35004),(49937, 34963)
+    -49957 | (49957, 43390),(49897, 43384)
+    -49954 | (49954, 1340),(49905, 1294)
+    -49944 | (49944, 25163),(49902, 25153)
+    -49907 | (49907, 30225),(49810, 30158)
+    -49902 | (49902, 41752),(49818, 41746)
+    -49887 | (49887, 24274),(49805, 24184)
+    -49853 | (49853, 18504),(49848, 18503)
+    -49847 | (49847, 7128),(49798, 7067)
+    -49843 | (49843, 5175),(49808, 5145)
+ (15 rows)
+ 
+ SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -49992 | (30746, 50040),(30727, 49992)
+    -49987 | (36311, 50073),(36258, 49987)
+    -49934 | (3531, 49962),(3463, 49934)
+    -49915 | (17954, 49975),(17865, 49915)
+    -49914 | (2168, 50012),(2108, 49914)
+    -49913 | (31287, 49923),(31236, 49913)
+    -49885 | (21551, 49983),(21492, 49885)
+    -49878 | (43925, 49912),(43888, 49878)
+    -49849 | (19128, 49932),(19112, 49849)
+    -49844 | (38266, 49852),(38233, 49844)
+    -49836 | (14913, 49873),(14849, 49836)
+    -49834 | (37595, 49849),(37581, 49834)
+    -49830 | (46151, 49848),(46058, 49830)
+    -49818 | (29261, 49910),(29247, 49818)
+ (15 rows)
+ 
+ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -50073 | (36311, 50073),(36258, 49987)
+    -50040 | (30746, 50040),(30727, 49992)
+    -50012 | (2168, 50012),(2108, 49914)
+    -49983 | (21551, 49983),(21492, 49885)
+    -49975 | (17954, 49975),(17865, 49915)
+    -49962 | (3531, 49962),(3463, 49934)
+    -49932 | (19128, 49932),(19112, 49849)
+    -49923 | (31287, 49923),(31236, 49913)
+    -49912 | (43925, 49912),(43888, 49878)
+    -49910 | (29261, 49910),(29247, 49818)
+    -49873 | (14913, 49873),(14849, 49836)
+    -49858 | (20007, 49858),(19921, 49778)
+    -49852 | (38266, 49852),(38233, 49844)
+    -49849 | (37595, 49849),(37581, 49834)
+ (15 rows)
+ 
+ -- Same queries with sequential scan (should give the same results as above)
+ SET enable_seqscan = ON;
+ SET enable_indexscan = OFF;
+ SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
+             c            |       dist       
+ -------------------------+------------------
+  (337, 455),(240, 359)   |                0
+  (1, 1)                  | 140.007142674936
+  (759, 187),(662, 163)   |              162
+  (948, 1201),(907, 1156) | 772.000647668122
+  (1444, 403),(1346, 344) |              846
+ (5 rows)
+ 
+ SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
+             c            | dist 
+ -------------------------+------
+  (337, 455),(240, 359)   |    0
+  (1, 1)                  |   99
+  (759, 187),(662, 163)   |  162
+  (948, 1201),(907, 1156) |  656
+  (1444, 403),(1346, 344) |  846
+ (5 rows)
+ 
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
+  (1, 1)                  |  198
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
  (5 rows)
  
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!         3 | (54, 38679),(3, 38602)
!        15 | (83, 10271),(15, 10265)
!        64 | (122, 46832),(64, 46762)
!        92 | (167, 17214),(92, 17184)
!       107 | (161, 24465),(107, 24374)
!       120 | (162, 26040),(120, 25963)
!       138 | (154, 4019),(138, 3990)
!       175 | (259, 1850),(175, 1820)
!       179 | (207, 40886),(179, 40879)
!       204 | (288, 49588),(204, 49571)
!       226 | (270, 32616),(226, 32607)
!       235 | (318, 31489),(235, 31404)
!       240 | (337, 455),(240, 359)
  (15 rows)
  
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!        54 | (54, 38679),(3, 38602)
!        83 | (83, 10271),(15, 10265)
!       122 | (122, 46832),(64, 46762)
!       154 | (154, 4019),(138, 3990)
!       161 | (161, 24465),(107, 24374)
!       162 | (162, 26040),(120, 25963)
!       167 | (167, 17214),(92, 17184)
!       207 | (207, 40886),(179, 40879)
!       259 | (259, 1850),(175, 1820)
!       270 | (270, 29508),(264, 29440)
!       270 | (270, 32616),(226, 32607)
!       288 | (288, 49588),(204, 49571)
!       318 | (318, 31489),(235, 31404)
  (15 rows)
  
! SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (100000)
!         1 | (1, 1)
!         6 | (30333, 50),(30273, 6)
!        43 | (43301, 75),(43227, 43)
!        51 | (19650, 142),(19630, 51)
!        81 | (2424, 160),(2424, 81)
!       108 | (3449, 171),(3354, 108)
!       109 | (18037, 155),(17941, 109)
!       114 | (28511, 208),(28479, 114)
!       118 | (19946, 217),(19941, 118)
!       139 | (16906, 191),(16816, 139)
!       163 | (759, 187),(662, 163)
!       181 | (22684, 266),(22656, 181)
!       213 | (24423, 255),(24360, 213)
!       222 | (45989, 249),(45910, 222)
  (15 rows)
  
! SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (100000)
!         1 | (1, 1)
!        50 | (30333, 50),(30273, 6)
!        75 | (43301, 75),(43227, 43)
!       142 | (19650, 142),(19630, 51)
!       155 | (18037, 155),(17941, 109)
!       160 | (2424, 160),(2424, 81)
!       171 | (3449, 171),(3354, 108)
!       187 | (759, 187),(662, 163)
!       191 | (16906, 191),(16816, 139)
!       208 | (28511, 208),(28479, 114)
!       217 | (19946, 217),(19941, 118)
!       249 | (45989, 249),(45910, 222)
!       255 | (24423, 255),(24360, 213)
!       266 | (22684, 266),(22656, 181)
  (15 rows)
  
! SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
!  ?column? |               c               
! ----------+-------------------------------
!   -100000 | (100000)
!    -49951 | (50027, 49230),(49951, 49214)
!    -49937 | (49980, 35004),(49937, 34963)
!    -49927 | (49985, 6436),(49927, 6338)
!    -49908 | (49999, 27218),(49908, 27176)
!    -49905 | (49954, 1340),(49905, 1294)
!    -49902 | (49944, 25163),(49902, 25153)
!    -49898 | (49981, 34876),(49898, 34786)
!    -49897 | (49957, 43390),(49897, 43384)
!    -49848 | (49853, 18504),(49848, 18503)
!    -49818 | (49902, 41752),(49818, 41746)
!    -49810 | (49907, 30225),(49810, 30158)
!    -49808 | (49843, 5175),(49808, 5145)
!    -49805 | (49887, 24274),(49805, 24184)
!    -49798 | (49847, 7128),(49798, 7067)
  (15 rows)
  
! SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
!  ?column? |               c               
! ----------+-------------------------------
!   -100000 | (100000)
!    -50027 | (50027, 49230),(49951, 49214)
!    -49999 | (49999, 27218),(49908, 27176)
!    -49985 | (49985, 6436),(49927, 6338)
!    -49981 | (49981, 34876),(49898, 34786)
!    -49980 | (49980, 35004),(49937, 34963)
!    -49957 | (49957, 43390),(49897, 43384)
!    -49954 | (49954, 1340),(49905, 1294)
!    -49944 | (49944, 25163),(49902, 25153)
!    -49907 | (49907, 30225),(49810, 30158)
!    -49902 | (49902, 41752),(49818, 41746)
!    -49887 | (49887, 24274),(49805, 24184)
!    -49853 | (49853, 18504),(49848, 18503)
!    -49847 | (49847, 7128),(49798, 7067)
!    -49843 | (49843, 5175),(49808, 5145)
! (15 rows)
! 
! SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
!  ?column? |               c               
! ----------+-------------------------------
!   -100000 | (0, 100000)
!    -49992 | (30746, 50040),(30727, 49992)
!    -49987 | (36311, 50073),(36258, 49987)
!    -49934 | (3531, 49962),(3463, 49934)
!    -49915 | (17954, 49975),(17865, 49915)
!    -49914 | (2168, 50012),(2108, 49914)
!    -49913 | (31287, 49923),(31236, 49913)
!    -49885 | (21551, 49983),(21492, 49885)
!    -49878 | (43925, 49912),(43888, 49878)
!    -49849 | (19128, 49932),(19112, 49849)
!    -49844 | (38266, 49852),(38233, 49844)
!    -49836 | (14913, 49873),(14849, 49836)
!    -49834 | (37595, 49849),(37581, 49834)
!    -49830 | (46151, 49848),(46058, 49830)
!    -49818 | (29261, 49910),(29247, 49818)
! (15 rows)
! 
! SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
!  ?column? |               c               
! ----------+-------------------------------
!   -100000 | (0, 100000)
!    -50073 | (36311, 50073),(36258, 49987)
!    -50040 | (30746, 50040),(30727, 49992)
!    -50012 | (2168, 50012),(2108, 49914)
!    -49983 | (21551, 49983),(21492, 49885)
!    -49975 | (17954, 49975),(17865, 49915)
!    -49962 | (3531, 49962),(3463, 49934)
!    -49932 | (19128, 49932),(19112, 49849)
!    -49923 | (31287, 49923),(31236, 49913)
!    -49912 | (43925, 49912),(43888, 49878)
!    -49910 | (29261, 49910),(29247, 49818)
!    -49873 | (14913, 49873),(14849, 49836)
!    -49858 | (20007, 49858),(19921, 49778)
!    -49852 | (38266, 49852),(38233, 49844)
!    -49849 | (37595, 49849),(37581, 49834)
  (15 rows)
  
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
new file mode 100644
index 58ea3ad..510d5a7
*** a/contrib/cube/sql/cube.sql
--- b/contrib/cube/sql/cube.sql
*************** SELECT * FROM test_cube WHERE c && '(300
*** 382,401 ****
  -- Test sorting
  SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c;
  
! -- kNN with index
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
  
! -- kNN-based sorting
! SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
! SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
! SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
! SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
  
! -- same thing for index with points
! CREATE TABLE test_point(c cube);
! INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
! CREATE INDEX ON test_point USING gist(c);
! SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
! SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
--- 382,420 ----
  -- Test sorting
  SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c;
  
! -- Test kNN
! INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
! SET enable_seqscan = OFF;
! SET enable_indexscan = ON;
! 
! -- Test different metrics
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
  
! -- Test sorting by coordinates
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
! SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
! SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
! SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
! SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
! SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
! SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
  
! -- Same queries with sequential scan (should give the same results as above)
! SET enable_seqscan = ON;
! SET enable_indexscan = OFF;
! 
! SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
! SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
! SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
! 
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
! SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
! SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
! SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
! SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
! SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
! SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
diff --git a/doc/src/sgml/cube.sgml b/doc/src/sgml/cube.sgml
new file mode 100644
index 46d8e4e..65887b1
*** a/doc/src/sgml/cube.sgml
--- b/doc/src/sgml/cube.sgml
***************
*** 186,195 ****
        <entry><literal>a ~&gt; n</literal></entry>
        <entry><type>float8</type></entry>
        <entry>
!         Get <replaceable>n</replaceable>-th coordinate in <quote>normalized</quote> cube
!         representation, in which the coordinates have been rearranged into
!         the form <quote>lower left &mdash; upper right</quote>; that is, the
!         smaller endpoint along each dimension appears first.
        </entry>
       </row>
  
--- 186,197 ----
        <entry><literal>a ~&gt; n</literal></entry>
        <entry><type>float8</type></entry>
        <entry>
!         Get <replaceable>n</replaceable>-th coordinate of cube in following way:
!         n = 2 * k - 1 means lower bound of <replaceable>k</replaceable>-th
!         dimension, n = 2 * k means upper bound of
!         <replaceable>k</replaceable>-th dimension.  Negative
!         <replaceable>n</replaceable> denotes inversed value of corresponding
!         positive coordinate.  This operator is designed for KNN-GiST support.
        </entry>
       </row>
  
#5Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Alexander Korotkov (#4)
Re: [HACKERS] CUBE seems a bit confused about ORDER BY

On 10/30/2017 03:04 PM, Alexander Korotkov wrote:

On Sun, Oct 29, 2017 at 1:30 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru <mailto:a.korotkov@postgrespro.ru>> wrote:

...

Thus, I heard no objection to my approach.  Attached patch changes ~>
operator in the proposed way and fixes KNN-GiST search for that.  I'm
going to register this patch to the nearest commitfest.

Seems fine to me, although perhaps it should be split into two parts.
One with the cube_coord_llur fixes, and then g_cube_distance changes
adding support for negative coordinates.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#6Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Tomas Vondra (#5)
Re: [HACKERS] CUBE seems a bit confused about ORDER BY

Hi, Tomas!

On Mon, Nov 20, 2017 at 4:09 AM, Tomas Vondra <tomas.vondra@2ndquadrant.com>
wrote:

Seems fine to me, although perhaps it should be split into two parts.
One with the cube_coord_llur fixes, and then g_cube_distance changes
adding support for negative coordinates.

Thank you for your feedback. I'll split this patch into two.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#7Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#6)
2 attachment(s)
Re: [HACKERS] CUBE seems a bit confused about ORDER BY

On Mon, Nov 20, 2017 at 1:59 PM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:

On Mon, Nov 20, 2017 at 4:09 AM, Tomas Vondra <
tomas.vondra@2ndquadrant.com> wrote:

Seems fine to me, although perhaps it should be split into two parts.
One with the cube_coord_llur fixes, and then g_cube_distance changes
adding support for negative coordinates.

Thank you for your feedback. I'll split this patch into two.

Please, find two patches attached.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-cube-knn-fix.patchapplication/octet-stream; name=0001-cube-knn-fix.patchDownload
diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
new file mode 100644
index 3e3d833..2b10cd4
*** a/contrib/cube/cube.c
--- b/contrib/cube/cube.c
*************** g_cube_distance(PG_FUNCTION_ARGS)
*** 1337,1351 ****
  
  	if (strategy == CubeKNNDistanceCoord)
  	{
  		int			coord = PG_GETARG_INT32(1);
  
! 		if (DIM(cube) == 0)
! 			retval = 0.0;
! 		else if (IS_POINT(cube))
! 			retval = cube->x[(coord - 1) % DIM(cube)];
  		else
! 			retval = Min(cube->x[(coord - 1) % DIM(cube)],
! 						 cube->x[(coord - 1) % DIM(cube) + DIM(cube)]);
  	}
  	else
  	{
--- 1337,1391 ----
  
  	if (strategy == CubeKNNDistanceCoord)
  	{
+ 		/*
+ 		 * Handle ordering by ~> operator.  See comments of cube_coord_llur()
+ 		 * for details
+ 		 */
  		int			coord = PG_GETARG_INT32(1);
+ 		bool		isLeaf = GistPageIsLeaf(entry->page);
  
! 		/* 0 is the only unsupported coordinate value */
! 		if (coord <= 0)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
! 					 errmsg("cube index %d is out of bounds", coord)));
! 
! 		if (coord <= 2 * DIM(cube))
! 		{
! 			/* dimension index */
! 			int		index = (coord - 1) / 2;
! 			/* whether this is upper bound (lower bound otherwise) */
! 			bool	upper = ((coord - 1) % 2 == 1);
! 
! 			if (IS_POINT(cube))
! 			{
! 				retval = cube->x[index];
! 			}
! 			else
! 			{
! 				if (isLeaf)
! 				{
! 					/* For leaf just return required upper/lower bound */
! 					if (upper)
! 						retval = Max(cube->x[index], cube->x[index + DIM(cube)]);
! 					else
! 						retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
! 				}
! 				else
! 				{
! 					/*
! 					 * For non-leaf we should always return lower bound,
! 					 * because even upper bound of a child in the subtree can
! 					 * be as small as our lower bound.
! 					 */
! 					retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
! 				}
! 			}
! 		}
  		else
! 		{
! 			retval = 0.0;
! 		}
  	}
  	else
  	{
*************** cube_coord(PG_FUNCTION_ARGS)
*** 1492,1534 ****
  }
  
  
! /*
!  * This function works like cube_coord(),
!  * but rearranges coordinates of corners to get cube representation
!  * in the form of (lower left, upper right).
!  * For historical reasons that extension allows us to create cubes in form
!  * ((2,1),(1,2)) and instead of normalizing such cube to ((1,1),(2,2)) it
!  * stores cube in original way. But to get cubes ordered by one of dimensions
!  * directly from the index without extra sort step we need some
!  * representation-independent coordinate getter. This function implements it.
   */
  Datum
  cube_coord_llur(PG_FUNCTION_ARGS)
  {
  	NDBOX	   *cube = PG_GETARG_NDBOX_P(0);
  	int			coord = PG_GETARG_INT32(1);
  
! 	if (coord <= 0 || coord > 2 * DIM(cube))
  		ereport(ERROR,
  				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
  				 errmsg("cube index %d is out of bounds", coord)));
  
! 	if (coord <= DIM(cube))
  	{
  		if (IS_POINT(cube))
! 			PG_RETURN_FLOAT8(cube->x[coord - 1]);
  		else
! 			PG_RETURN_FLOAT8(Min(cube->x[coord - 1],
! 								 cube->x[coord - 1 + DIM(cube)]));
  	}
  	else
  	{
! 		if (IS_POINT(cube))
! 			PG_RETURN_FLOAT8(cube->x[(coord - 1) % DIM(cube)]);
! 		else
! 			PG_RETURN_FLOAT8(Max(cube->x[coord - 1],
! 								 cube->x[coord - 1 - DIM(cube)]));
  	}
  }
  
  /* Increase or decrease box size by a radius in at least n dimensions. */
--- 1532,1603 ----
  }
  
  
! /*----
!  * This function works like cube_coord(), but rearranges coordinates in the
!  * way suitable to support coordinate ordering using KNN-GiST.  For historical
!  * reasons this extension allows us to create cubes in form ((2,1),(1,2)) and
!  * instead of normalizing such cube to ((1,1),(2,2)) it stores cube in original
!  * way.  But in order to get cubes ordered by one of dimensions from the index
!  * without explicit sort step we need this representation-independent coordinate
!  * getter.  Moreover, indexed dataset may contain cubes of different dimensions
!  * number.  Accordingly, this corrdinate getter should be able to return
!  * lower/upper bound for particular dimension independently on number of cube
!  * dimenstions.
!  *
!  * Long stoty short, this function uses following meaning of coordinates:
!  * # (2 * N - 1) -- lower bound of Nth dimension,
!  * # (2 * N) -- upper bound of Nth dimenstion.
!  *
!  * When given coordinate exceeds number of cube dimensions, then 0 returned
!  * (reproducing logic of GiST indexing of variable-length cubes).
   */
  Datum
  cube_coord_llur(PG_FUNCTION_ARGS)
  {
  	NDBOX	   *cube = PG_GETARG_NDBOX_P(0);
  	int			coord = PG_GETARG_INT32(1);
+ 	bool		inverse = false;
+ 	float8		result;
  
! 	/* 0 is the only unsupported coordinate value */
! 	if (coord <= 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
  				 errmsg("cube index %d is out of bounds", coord)));
  
! 	if (coord <= 2 * DIM(cube))
  	{
+ 		/* dimension index */
+ 		int		index = (coord - 1) / 2;
+ 		/* whether this is upper bound (lower bound otherwise) */
+ 		bool	upper = ((coord - 1) % 2 == 1);
+ 
  		if (IS_POINT(cube))
! 		{
! 			result = cube->x[index];
! 		}
  		else
! 		{
! 			if (upper)
! 				result = Max(cube->x[index], cube->x[index + DIM(cube)]);
! 			else
! 				result = Min(cube->x[index], cube->x[index + DIM(cube)]);
! 		}
  	}
  	else
  	{
! 		/*
! 		 * Return zero if coordinate is out of bound.  That reproduces logic of
! 		 * how cubes with low dimension number are expanded duing GiST indexing.
! 		 */
! 		result = 0.0;
  	}
+ 
+ 	/* Inverse value if needed */
+ 	if (inverse)
+ 		result = -result;
+ 
+ 	PG_RETURN_FLOAT8(result);
  }
  
  /* Increase or decrease box size by a radius in at least n dimensions. */
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
new file mode 100644
index 328b3b5..8f85fc0
*** a/contrib/cube/expected/cube.out
--- b/contrib/cube/expected/cube.out
*************** SELECT cube(array[40,50,60], array[10,20
*** 1532,1556 ****
  SELECT cube(array[10,20,30], array[40,50,60])~>2;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>2;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[10,20,30], array[40,50,60])~>3;
   ?column? 
  ----------
!        30
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>3;
   ?column? 
  ----------
!        30
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>0;
--- 1532,1556 ----
  SELECT cube(array[10,20,30], array[40,50,60])~>2;
   ?column? 
  ----------
!        40
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>2;
   ?column? 
  ----------
!        40
  (1 row)
  
  SELECT cube(array[10,20,30], array[40,50,60])~>3;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>3;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>0;
*************** ERROR:  cube index 0 is out of bounds
*** 1558,1564 ****
  SELECT cube(array[40,50,60], array[10,20,30])~>4;
   ?column? 
  ----------
!        40
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
--- 1558,1564 ----
  SELECT cube(array[40,50,60], array[10,20,30])~>4;
   ?column? 
  ----------
!        50
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
*************** SELECT * FROM test_cube WHERE c && '(300
*** 1589,1613 ****
   (2424, 160),(2424, 81)
  (5 rows)
  
! -- kNN with index
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            |       dist       
  -------------------------+------------------
   (337, 455),(240, 359)   |                0
   (759, 187),(662, 163)   |              162
   (948, 1201),(907, 1156) | 772.000647668122
   (1444, 403),(1346, 344) |              846
-  (369, 1457),(278, 1409) |              909
  (5 rows)
  
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
   (948, 1201),(907, 1156) |  656
   (1444, 403),(1346, 344) |  846
-  (369, 1457),(278, 1409) |  909
  (5 rows)
  
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
--- 1589,1617 ----
   (2424, 160),(2424, 81)
  (5 rows)
  
! -- Test kNN
! INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
! SET enable_seqscan = OFF;
! SET enable_indexscan = ON;
! -- Test different metrics
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            |       dist       
  -------------------------+------------------
   (337, 455),(240, 359)   |                0
+  (1, 1)                  | 140.007142674936
   (759, 187),(662, 163)   |              162
   (948, 1201),(907, 1156) | 772.000647668122
   (1444, 403),(1346, 344) |              846
  (5 rows)
  
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
+  (1, 1)                  |   99
   (759, 187),(662, 163)   |  162
   (948, 1201),(907, 1156) |  656
   (1444, 403),(1346, 344) |  846
  (5 rows)
  
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
*************** SELECT *, c <#> '(100, 100),(500, 500)':
*** 1615,1747 ****
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
-  (948, 1201),(907, 1156) | 1063
  (5 rows)
  
! -- kNN-based sorting
! SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
!              c             
! ---------------------------
!  (54, 38679),(3, 38602)
!  (83, 10271),(15, 10265)
!  (122, 46832),(64, 46762)
!  (167, 17214),(92, 17184)
!  (161, 24465),(107, 24374)
!  (162, 26040),(120, 25963)
!  (154, 4019),(138, 3990)
!  (259, 1850),(175, 1820)
!  (207, 40886),(179, 40879)
!  (288, 49588),(204, 49571)
!  (270, 32616),(226, 32607)
!  (318, 31489),(235, 31404)
!  (337, 455),(240, 359)
!  (270, 29508),(264, 29440)
!  (369, 1457),(278, 1409)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
!              c             
! ---------------------------
!  (30333, 50),(30273, 6)
!  (43301, 75),(43227, 43)
!  (19650, 142),(19630, 51)
!  (2424, 160),(2424, 81)
!  (3449, 171),(3354, 108)
!  (18037, 155),(17941, 109)
!  (28511, 208),(28479, 114)
!  (19946, 217),(19941, 118)
!  (16906, 191),(16816, 139)
!  (759, 187),(662, 163)
!  (22684, 266),(22656, 181)
!  (24423, 255),(24360, 213)
!  (45989, 249),(45910, 222)
!  (11399, 377),(11360, 294)
!  (12162, 389),(12103, 309)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
!                c               
! -------------------------------
!  (50027, 49230),(49951, 49214)
!  (49980, 35004),(49937, 34963)
!  (49985, 6436),(49927, 6338)
!  (49999, 27218),(49908, 27176)
!  (49954, 1340),(49905, 1294)
!  (49944, 25163),(49902, 25153)
!  (49981, 34876),(49898, 34786)
!  (49957, 43390),(49897, 43384)
!  (49853, 18504),(49848, 18503)
!  (49902, 41752),(49818, 41746)
!  (49907, 30225),(49810, 30158)
!  (49843, 5175),(49808, 5145)
!  (49887, 24274),(49805, 24184)
!  (49847, 7128),(49798, 7067)
!  (49820, 7990),(49771, 7967)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
!                c               
! -------------------------------
!  (36311, 50073),(36258, 49987)
!  (30746, 50040),(30727, 49992)
!  (2168, 50012),(2108, 49914)
!  (21551, 49983),(21492, 49885)
!  (17954, 49975),(17865, 49915)
!  (3531, 49962),(3463, 49934)
!  (19128, 49932),(19112, 49849)
!  (31287, 49923),(31236, 49913)
!  (43925, 49912),(43888, 49878)
!  (29261, 49910),(29247, 49818)
!  (14913, 49873),(14849, 49836)
!  (20007, 49858),(19921, 49778)
!  (38266, 49852),(38233, 49844)
!  (37595, 49849),(37581, 49834)
!  (46151, 49848),(46058, 49830)
  (15 rows)
  
! -- same thing for index with points
! CREATE TABLE test_point(c cube);
! INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
! CREATE INDEX ON test_point USING gist(c);
! SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
!             c             
! --------------------------
!  (54, 38679, 3, 38602)
!  (83, 10271, 15, 10265)
!  (122, 46832, 64, 46762)
!  (154, 4019, 138, 3990)
!  (161, 24465, 107, 24374)
!  (162, 26040, 120, 25963)
!  (167, 17214, 92, 17184)
!  (207, 40886, 179, 40879)
!  (259, 1850, 175, 1820)
!  (270, 29508, 264, 29440)
!  (270, 32616, 226, 32607)
!  (288, 49588, 204, 49571)
!  (318, 31489, 235, 31404)
!  (326, 18837, 285, 18817)
!  (337, 455, 240, 359)
  (15 rows)
  
! SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
!               c               
! ------------------------------
!  (30746, 50040, 30727, 49992)
!  (36311, 50073, 36258, 49987)
!  (3531, 49962, 3463, 49934)
!  (17954, 49975, 17865, 49915)
!  (2168, 50012, 2108, 49914)
!  (31287, 49923, 31236, 49913)
!  (21551, 49983, 21492, 49885)
!  (43925, 49912, 43888, 49878)
!  (19128, 49932, 19112, 49849)
!  (38266, 49852, 38233, 49844)
!  (14913, 49873, 14849, 49836)
!  (37595, 49849, 37581, 49834)
!  (46151, 49848, 46058, 49830)
!  (29261, 49910, 29247, 49818)
!  (19233, 49824, 19185, 49794)
  (15 rows)
  
--- 1619,1820 ----
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
+  (1, 1)                  |  198
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
  (5 rows)
  
! -- Test sorting by coordinates
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!         3 | (54, 38679),(3, 38602)
!        15 | (83, 10271),(15, 10265)
!        64 | (122, 46832),(64, 46762)
!        92 | (167, 17214),(92, 17184)
!       107 | (161, 24465),(107, 24374)
!       120 | (162, 26040),(120, 25963)
!       138 | (154, 4019),(138, 3990)
!       175 | (259, 1850),(175, 1820)
!       179 | (207, 40886),(179, 40879)
!       204 | (288, 49588),(204, 49571)
!       226 | (270, 32616),(226, 32607)
!       235 | (318, 31489),(235, 31404)
!       240 | (337, 455),(240, 359)
  (15 rows)
  
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!        54 | (54, 38679),(3, 38602)
!        83 | (83, 10271),(15, 10265)
!       122 | (122, 46832),(64, 46762)
!       154 | (154, 4019),(138, 3990)
!       161 | (161, 24465),(107, 24374)
!       162 | (162, 26040),(120, 25963)
!       167 | (167, 17214),(92, 17184)
!       207 | (207, 40886),(179, 40879)
!       259 | (259, 1850),(175, 1820)
!       270 | (270, 29508),(264, 29440)
!       270 | (270, 32616),(226, 32607)
!       288 | (288, 49588),(204, 49571)
!       318 | (318, 31489),(235, 31404)
  (15 rows)
  
! SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (100000)
!         1 | (1, 1)
!         6 | (30333, 50),(30273, 6)
!        43 | (43301, 75),(43227, 43)
!        51 | (19650, 142),(19630, 51)
!        81 | (2424, 160),(2424, 81)
!       108 | (3449, 171),(3354, 108)
!       109 | (18037, 155),(17941, 109)
!       114 | (28511, 208),(28479, 114)
!       118 | (19946, 217),(19941, 118)
!       139 | (16906, 191),(16816, 139)
!       163 | (759, 187),(662, 163)
!       181 | (22684, 266),(22656, 181)
!       213 | (24423, 255),(24360, 213)
!       222 | (45989, 249),(45910, 222)
  (15 rows)
  
! SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (100000)
!         1 | (1, 1)
!        50 | (30333, 50),(30273, 6)
!        75 | (43301, 75),(43227, 43)
!       142 | (19650, 142),(19630, 51)
!       155 | (18037, 155),(17941, 109)
!       160 | (2424, 160),(2424, 81)
!       171 | (3449, 171),(3354, 108)
!       187 | (759, 187),(662, 163)
!       191 | (16906, 191),(16816, 139)
!       208 | (28511, 208),(28479, 114)
!       217 | (19946, 217),(19941, 118)
!       249 | (45989, 249),(45910, 222)
!       255 | (24423, 255),(24360, 213)
!       266 | (22684, 266),(22656, 181)
  (15 rows)
  
! -- Same queries with sequential scan (should give the same results as above)
! SET enable_seqscan = ON;
! SET enable_indexscan = OFF;
! SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
!             c            |       dist       
! -------------------------+------------------
!  (337, 455),(240, 359)   |                0
!  (1, 1)                  | 140.007142674936
!  (759, 187),(662, 163)   |              162
!  (948, 1201),(907, 1156) | 772.000647668122
!  (1444, 403),(1346, 344) |              846
! (5 rows)
! 
! SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
!             c            | dist 
! -------------------------+------
!  (337, 455),(240, 359)   |    0
!  (1, 1)                  |   99
!  (759, 187),(662, 163)   |  162
!  (948, 1201),(907, 1156) |  656
!  (1444, 403),(1346, 344) |  846
! (5 rows)
! 
! SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
!             c            | dist 
! -------------------------+------
!  (337, 455),(240, 359)   |    0
!  (759, 187),(662, 163)   |  162
!  (1, 1)                  |  198
!  (1444, 403),(1346, 344) |  846
!  (369, 1457),(278, 1409) |  909
! (5 rows)
! 
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!         3 | (54, 38679),(3, 38602)
!        15 | (83, 10271),(15, 10265)
!        64 | (122, 46832),(64, 46762)
!        92 | (167, 17214),(92, 17184)
!       107 | (161, 24465),(107, 24374)
!       120 | (162, 26040),(120, 25963)
!       138 | (154, 4019),(138, 3990)
!       175 | (259, 1850),(175, 1820)
!       179 | (207, 40886),(179, 40879)
!       204 | (288, 49588),(204, 49571)
!       226 | (270, 32616),(226, 32607)
!       235 | (318, 31489),(235, 31404)
!       240 | (337, 455),(240, 359)
  (15 rows)
  
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!        54 | (54, 38679),(3, 38602)
!        83 | (83, 10271),(15, 10265)
!       122 | (122, 46832),(64, 46762)
!       154 | (154, 4019),(138, 3990)
!       161 | (161, 24465),(107, 24374)
!       162 | (162, 26040),(120, 25963)
!       167 | (167, 17214),(92, 17184)
!       207 | (207, 40886),(179, 40879)
!       259 | (259, 1850),(175, 1820)
!       270 | (270, 29508),(264, 29440)
!       270 | (270, 32616),(226, 32607)
!       288 | (288, 49588),(204, 49571)
!       318 | (318, 31489),(235, 31404)
! (15 rows)
! 
! SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (100000)
!         1 | (1, 1)
!         6 | (30333, 50),(30273, 6)
!        43 | (43301, 75),(43227, 43)
!        51 | (19650, 142),(19630, 51)
!        81 | (2424, 160),(2424, 81)
!       108 | (3449, 171),(3354, 108)
!       109 | (18037, 155),(17941, 109)
!       114 | (28511, 208),(28479, 114)
!       118 | (19946, 217),(19941, 118)
!       139 | (16906, 191),(16816, 139)
!       163 | (759, 187),(662, 163)
!       181 | (22684, 266),(22656, 181)
!       213 | (24423, 255),(24360, 213)
!       222 | (45989, 249),(45910, 222)
! (15 rows)
! 
! SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (100000)
!         1 | (1, 1)
!        50 | (30333, 50),(30273, 6)
!        75 | (43301, 75),(43227, 43)
!       142 | (19650, 142),(19630, 51)
!       155 | (18037, 155),(17941, 109)
!       160 | (2424, 160),(2424, 81)
!       171 | (3449, 171),(3354, 108)
!       187 | (759, 187),(662, 163)
!       191 | (16906, 191),(16816, 139)
!       208 | (28511, 208),(28479, 114)
!       217 | (19946, 217),(19941, 118)
!       249 | (45989, 249),(45910, 222)
!       255 | (24423, 255),(24360, 213)
!       266 | (22684, 266),(22656, 181)
  (15 rows)
  
diff --git a/contrib/cube/expected/cube_2.out b/contrib/cube/expected/cube_2.out
new file mode 100644
index 1aa5cf2..c6ce3a3
*** a/contrib/cube/expected/cube_2.out
--- b/contrib/cube/expected/cube_2.out
*************** SELECT * FROM test_cube WHERE c && '(300
*** 1589,1747 ****
   (2424, 160),(2424, 81)
  (5 rows)
  
! -- kNN with index
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            |       dist       
  -------------------------+------------------
   (337, 455),(240, 359)   |                0
   (759, 187),(662, 163)   |              162
   (948, 1201),(907, 1156) | 772.000647668122
   (1444, 403),(1346, 344) |              846
-  (369, 1457),(278, 1409) |              909
  (5 rows)
  
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
   (948, 1201),(907, 1156) |  656
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
  (5 rows)
  
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
-  (948, 1201),(907, 1156) | 1063
  (5 rows)
  
! -- kNN-based sorting
! SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
!              c             
! ---------------------------
!  (54, 38679),(3, 38602)
!  (83, 10271),(15, 10265)
!  (122, 46832),(64, 46762)
!  (167, 17214),(92, 17184)
!  (161, 24465),(107, 24374)
!  (162, 26040),(120, 25963)
!  (154, 4019),(138, 3990)
!  (259, 1850),(175, 1820)
!  (207, 40886),(179, 40879)
!  (288, 49588),(204, 49571)
!  (270, 32616),(226, 32607)
!  (318, 31489),(235, 31404)
!  (337, 455),(240, 359)
!  (270, 29508),(264, 29440)
!  (369, 1457),(278, 1409)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
!              c             
! ---------------------------
!  (30333, 50),(30273, 6)
!  (43301, 75),(43227, 43)
!  (19650, 142),(19630, 51)
!  (2424, 160),(2424, 81)
!  (3449, 171),(3354, 108)
!  (18037, 155),(17941, 109)
!  (28511, 208),(28479, 114)
!  (19946, 217),(19941, 118)
!  (16906, 191),(16816, 139)
!  (759, 187),(662, 163)
!  (22684, 266),(22656, 181)
!  (24423, 255),(24360, 213)
!  (45989, 249),(45910, 222)
!  (11399, 377),(11360, 294)
!  (12162, 389),(12103, 309)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
!                c               
! -------------------------------
!  (50027, 49230),(49951, 49214)
!  (49980, 35004),(49937, 34963)
!  (49985, 6436),(49927, 6338)
!  (49999, 27218),(49908, 27176)
!  (49954, 1340),(49905, 1294)
!  (49944, 25163),(49902, 25153)
!  (49981, 34876),(49898, 34786)
!  (49957, 43390),(49897, 43384)
!  (49853, 18504),(49848, 18503)
!  (49902, 41752),(49818, 41746)
!  (49907, 30225),(49810, 30158)
!  (49843, 5175),(49808, 5145)
!  (49887, 24274),(49805, 24184)
!  (49847, 7128),(49798, 7067)
!  (49820, 7990),(49771, 7967)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
!                c               
! -------------------------------
!  (36311, 50073),(36258, 49987)
!  (30746, 50040),(30727, 49992)
!  (2168, 50012),(2108, 49914)
!  (21551, 49983),(21492, 49885)
!  (17954, 49975),(17865, 49915)
!  (3531, 49962),(3463, 49934)
!  (19128, 49932),(19112, 49849)
!  (31287, 49923),(31236, 49913)
!  (43925, 49912),(43888, 49878)
!  (29261, 49910),(29247, 49818)
!  (14913, 49873),(14849, 49836)
!  (20007, 49858),(19921, 49778)
!  (38266, 49852),(38233, 49844)
!  (37595, 49849),(37581, 49834)
!  (46151, 49848),(46058, 49830)
  (15 rows)
  
! -- same thing for index with points
! CREATE TABLE test_point(c cube);
! INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
! CREATE INDEX ON test_point USING gist(c);
! SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
!             c             
! --------------------------
!  (54, 38679, 3, 38602)
!  (83, 10271, 15, 10265)
!  (122, 46832, 64, 46762)
!  (154, 4019, 138, 3990)
!  (161, 24465, 107, 24374)
!  (162, 26040, 120, 25963)
!  (167, 17214, 92, 17184)
!  (207, 40886, 179, 40879)
!  (259, 1850, 175, 1820)
!  (270, 29508, 264, 29440)
!  (270, 32616, 226, 32607)
!  (288, 49588, 204, 49571)
!  (318, 31489, 235, 31404)
!  (326, 18837, 285, 18817)
!  (337, 455, 240, 359)
  (15 rows)
  
! SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
!               c               
! ------------------------------
!  (30746, 50040, 30727, 49992)
!  (36311, 50073, 36258, 49987)
!  (3531, 49962, 3463, 49934)
!  (17954, 49975, 17865, 49915)
!  (2168, 50012, 2108, 49914)
!  (31287, 49923, 31236, 49913)
!  (21551, 49983, 21492, 49885)
!  (43925, 49912, 43888, 49878)
!  (19128, 49932, 19112, 49849)
!  (38266, 49852, 38233, 49844)
!  (14913, 49873, 14849, 49836)
!  (37595, 49849, 37581, 49834)
!  (46151, 49848, 46058, 49830)
!  (29261, 49910, 29247, 49818)
!  (19233, 49824, 19185, 49794)
  (15 rows)
  
--- 1589,1980 ----
   (2424, 160),(2424, 81)
  (5 rows)
  
! -- Test kNN
! INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
! SET enable_seqscan = OFF;
! SET enable_indexscan = ON;
! -- Test different metrics
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            |       dist       
  -------------------------+------------------
   (337, 455),(240, 359)   |                0
+  (1, 1)                  | 140.007142674936
   (759, 187),(662, 163)   |              162
   (948, 1201),(907, 1156) | 772.000647668122
   (1444, 403),(1346, 344) |              846
  (5 rows)
  
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
+  (1, 1)                  |   99
   (759, 187),(662, 163)   |  162
   (948, 1201),(907, 1156) |  656
   (1444, 403),(1346, 344) |  846
+ (5 rows)
+ 
+ SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
+             c            | dist 
+ -------------------------+------
+  (337, 455),(240, 359)   |    0
+  (759, 187),(662, 163)   |  162
+  (1, 1)                  |  198
+  (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
  (5 rows)
  
+ -- Test sorting by coordinates
+ SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (0, 100000)
+         1 | (1, 1)
+         3 | (54, 38679),(3, 38602)
+        15 | (83, 10271),(15, 10265)
+        64 | (122, 46832),(64, 46762)
+        92 | (167, 17214),(92, 17184)
+       107 | (161, 24465),(107, 24374)
+       120 | (162, 26040),(120, 25963)
+       138 | (154, 4019),(138, 3990)
+       175 | (259, 1850),(175, 1820)
+       179 | (207, 40886),(179, 40879)
+       204 | (288, 49588),(204, 49571)
+       226 | (270, 32616),(226, 32607)
+       235 | (318, 31489),(235, 31404)
+       240 | (337, 455),(240, 359)
+ (15 rows)
+ 
+ SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (0, 100000)
+         1 | (1, 1)
+        54 | (54, 38679),(3, 38602)
+        83 | (83, 10271),(15, 10265)
+       122 | (122, 46832),(64, 46762)
+       154 | (154, 4019),(138, 3990)
+       161 | (161, 24465),(107, 24374)
+       162 | (162, 26040),(120, 25963)
+       167 | (167, 17214),(92, 17184)
+       207 | (207, 40886),(179, 40879)
+       259 | (259, 1850),(175, 1820)
+       270 | (270, 29508),(264, 29440)
+       270 | (270, 32616),(226, 32607)
+       288 | (288, 49588),(204, 49571)
+       318 | (318, 31489),(235, 31404)
+ (15 rows)
+ 
+ SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (100000)
+         1 | (1, 1)
+         6 | (30333, 50),(30273, 6)
+        43 | (43301, 75),(43227, 43)
+        51 | (19650, 142),(19630, 51)
+        81 | (2424, 160),(2424, 81)
+       108 | (3449, 171),(3354, 108)
+       109 | (18037, 155),(17941, 109)
+       114 | (28511, 208),(28479, 114)
+       118 | (19946, 217),(19941, 118)
+       139 | (16906, 191),(16816, 139)
+       163 | (759, 187),(662, 163)
+       181 | (22684, 266),(22656, 181)
+       213 | (24423, 255),(24360, 213)
+       222 | (45989, 249),(45910, 222)
+ (15 rows)
+ 
+ SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (100000)
+         1 | (1, 1)
+        50 | (30333, 50),(30273, 6)
+        75 | (43301, 75),(43227, 43)
+       142 | (19650, 142),(19630, 51)
+       155 | (18037, 155),(17941, 109)
+       160 | (2424, 160),(2424, 81)
+       171 | (3449, 171),(3354, 108)
+       187 | (759, 187),(662, 163)
+       191 | (16906, 191),(16816, 139)
+       208 | (28511, 208),(28479, 114)
+       217 | (19946, 217),(19941, 118)
+       249 | (45989, 249),(45910, 222)
+       255 | (24423, 255),(24360, 213)
+       266 | (22684, 266),(22656, 181)
+ (15 rows)
+ 
+ SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -49951 | (50027, 49230),(49951, 49214)
+    -49937 | (49980, 35004),(49937, 34963)
+    -49927 | (49985, 6436),(49927, 6338)
+    -49908 | (49999, 27218),(49908, 27176)
+    -49905 | (49954, 1340),(49905, 1294)
+    -49902 | (49944, 25163),(49902, 25153)
+    -49898 | (49981, 34876),(49898, 34786)
+    -49897 | (49957, 43390),(49897, 43384)
+    -49848 | (49853, 18504),(49848, 18503)
+    -49818 | (49902, 41752),(49818, 41746)
+    -49810 | (49907, 30225),(49810, 30158)
+    -49808 | (49843, 5175),(49808, 5145)
+    -49805 | (49887, 24274),(49805, 24184)
+    -49798 | (49847, 7128),(49798, 7067)
+ (15 rows)
+ 
+ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -50027 | (50027, 49230),(49951, 49214)
+    -49999 | (49999, 27218),(49908, 27176)
+    -49985 | (49985, 6436),(49927, 6338)
+    -49981 | (49981, 34876),(49898, 34786)
+    -49980 | (49980, 35004),(49937, 34963)
+    -49957 | (49957, 43390),(49897, 43384)
+    -49954 | (49954, 1340),(49905, 1294)
+    -49944 | (49944, 25163),(49902, 25153)
+    -49907 | (49907, 30225),(49810, 30158)
+    -49902 | (49902, 41752),(49818, 41746)
+    -49887 | (49887, 24274),(49805, 24184)
+    -49853 | (49853, 18504),(49848, 18503)
+    -49847 | (49847, 7128),(49798, 7067)
+    -49843 | (49843, 5175),(49808, 5145)
+ (15 rows)
+ 
+ SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -49992 | (30746, 50040),(30727, 49992)
+    -49987 | (36311, 50073),(36258, 49987)
+    -49934 | (3531, 49962),(3463, 49934)
+    -49915 | (17954, 49975),(17865, 49915)
+    -49914 | (2168, 50012),(2108, 49914)
+    -49913 | (31287, 49923),(31236, 49913)
+    -49885 | (21551, 49983),(21492, 49885)
+    -49878 | (43925, 49912),(43888, 49878)
+    -49849 | (19128, 49932),(19112, 49849)
+    -49844 | (38266, 49852),(38233, 49844)
+    -49836 | (14913, 49873),(14849, 49836)
+    -49834 | (37595, 49849),(37581, 49834)
+    -49830 | (46151, 49848),(46058, 49830)
+    -49818 | (29261, 49910),(29247, 49818)
+ (15 rows)
+ 
+ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -50073 | (36311, 50073),(36258, 49987)
+    -50040 | (30746, 50040),(30727, 49992)
+    -50012 | (2168, 50012),(2108, 49914)
+    -49983 | (21551, 49983),(21492, 49885)
+    -49975 | (17954, 49975),(17865, 49915)
+    -49962 | (3531, 49962),(3463, 49934)
+    -49932 | (19128, 49932),(19112, 49849)
+    -49923 | (31287, 49923),(31236, 49913)
+    -49912 | (43925, 49912),(43888, 49878)
+    -49910 | (29261, 49910),(29247, 49818)
+    -49873 | (14913, 49873),(14849, 49836)
+    -49858 | (20007, 49858),(19921, 49778)
+    -49852 | (38266, 49852),(38233, 49844)
+    -49849 | (37595, 49849),(37581, 49834)
+ (15 rows)
+ 
+ -- Same queries with sequential scan (should give the same results as above)
+ SET enable_seqscan = ON;
+ SET enable_indexscan = OFF;
+ SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
+             c            |       dist       
+ -------------------------+------------------
+  (337, 455),(240, 359)   |                0
+  (1, 1)                  | 140.007142674936
+  (759, 187),(662, 163)   |              162
+  (948, 1201),(907, 1156) | 772.000647668122
+  (1444, 403),(1346, 344) |              846
+ (5 rows)
+ 
+ SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
+             c            | dist 
+ -------------------------+------
+  (337, 455),(240, 359)   |    0
+  (1, 1)                  |   99
+  (759, 187),(662, 163)   |  162
+  (948, 1201),(907, 1156) |  656
+  (1444, 403),(1346, 344) |  846
+ (5 rows)
+ 
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
+  (1, 1)                  |  198
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
  (5 rows)
  
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!         3 | (54, 38679),(3, 38602)
!        15 | (83, 10271),(15, 10265)
!        64 | (122, 46832),(64, 46762)
!        92 | (167, 17214),(92, 17184)
!       107 | (161, 24465),(107, 24374)
!       120 | (162, 26040),(120, 25963)
!       138 | (154, 4019),(138, 3990)
!       175 | (259, 1850),(175, 1820)
!       179 | (207, 40886),(179, 40879)
!       204 | (288, 49588),(204, 49571)
!       226 | (270, 32616),(226, 32607)
!       235 | (318, 31489),(235, 31404)
!       240 | (337, 455),(240, 359)
  (15 rows)
  
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!        54 | (54, 38679),(3, 38602)
!        83 | (83, 10271),(15, 10265)
!       122 | (122, 46832),(64, 46762)
!       154 | (154, 4019),(138, 3990)
!       161 | (161, 24465),(107, 24374)
!       162 | (162, 26040),(120, 25963)
!       167 | (167, 17214),(92, 17184)
!       207 | (207, 40886),(179, 40879)
!       259 | (259, 1850),(175, 1820)
!       270 | (270, 29508),(264, 29440)
!       270 | (270, 32616),(226, 32607)
!       288 | (288, 49588),(204, 49571)
!       318 | (318, 31489),(235, 31404)
  (15 rows)
  
! SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (100000)
!         1 | (1, 1)
!         6 | (30333, 50),(30273, 6)
!        43 | (43301, 75),(43227, 43)
!        51 | (19650, 142),(19630, 51)
!        81 | (2424, 160),(2424, 81)
!       108 | (3449, 171),(3354, 108)
!       109 | (18037, 155),(17941, 109)
!       114 | (28511, 208),(28479, 114)
!       118 | (19946, 217),(19941, 118)
!       139 | (16906, 191),(16816, 139)
!       163 | (759, 187),(662, 163)
!       181 | (22684, 266),(22656, 181)
!       213 | (24423, 255),(24360, 213)
!       222 | (45989, 249),(45910, 222)
  (15 rows)
  
! SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (100000)
!         1 | (1, 1)
!        50 | (30333, 50),(30273, 6)
!        75 | (43301, 75),(43227, 43)
!       142 | (19650, 142),(19630, 51)
!       155 | (18037, 155),(17941, 109)
!       160 | (2424, 160),(2424, 81)
!       171 | (3449, 171),(3354, 108)
!       187 | (759, 187),(662, 163)
!       191 | (16906, 191),(16816, 139)
!       208 | (28511, 208),(28479, 114)
!       217 | (19946, 217),(19941, 118)
!       249 | (45989, 249),(45910, 222)
!       255 | (24423, 255),(24360, 213)
!       266 | (22684, 266),(22656, 181)
  (15 rows)
  
! SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
!  ?column? |               c               
! ----------+-------------------------------
!   -100000 | (100000)
!    -49951 | (50027, 49230),(49951, 49214)
!    -49937 | (49980, 35004),(49937, 34963)
!    -49927 | (49985, 6436),(49927, 6338)
!    -49908 | (49999, 27218),(49908, 27176)
!    -49905 | (49954, 1340),(49905, 1294)
!    -49902 | (49944, 25163),(49902, 25153)
!    -49898 | (49981, 34876),(49898, 34786)
!    -49897 | (49957, 43390),(49897, 43384)
!    -49848 | (49853, 18504),(49848, 18503)
!    -49818 | (49902, 41752),(49818, 41746)
!    -49810 | (49907, 30225),(49810, 30158)
!    -49808 | (49843, 5175),(49808, 5145)
!    -49805 | (49887, 24274),(49805, 24184)
!    -49798 | (49847, 7128),(49798, 7067)
  (15 rows)
  
! SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
!  ?column? |               c               
! ----------+-------------------------------
!   -100000 | (100000)
!    -50027 | (50027, 49230),(49951, 49214)
!    -49999 | (49999, 27218),(49908, 27176)
!    -49985 | (49985, 6436),(49927, 6338)
!    -49981 | (49981, 34876),(49898, 34786)
!    -49980 | (49980, 35004),(49937, 34963)
!    -49957 | (49957, 43390),(49897, 43384)
!    -49954 | (49954, 1340),(49905, 1294)
!    -49944 | (49944, 25163),(49902, 25153)
!    -49907 | (49907, 30225),(49810, 30158)
!    -49902 | (49902, 41752),(49818, 41746)
!    -49887 | (49887, 24274),(49805, 24184)
!    -49853 | (49853, 18504),(49848, 18503)
!    -49847 | (49847, 7128),(49798, 7067)
!    -49843 | (49843, 5175),(49808, 5145)
! (15 rows)
! 
! SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
!  ?column? |               c               
! ----------+-------------------------------
!   -100000 | (0, 100000)
!    -49992 | (30746, 50040),(30727, 49992)
!    -49987 | (36311, 50073),(36258, 49987)
!    -49934 | (3531, 49962),(3463, 49934)
!    -49915 | (17954, 49975),(17865, 49915)
!    -49914 | (2168, 50012),(2108, 49914)
!    -49913 | (31287, 49923),(31236, 49913)
!    -49885 | (21551, 49983),(21492, 49885)
!    -49878 | (43925, 49912),(43888, 49878)
!    -49849 | (19128, 49932),(19112, 49849)
!    -49844 | (38266, 49852),(38233, 49844)
!    -49836 | (14913, 49873),(14849, 49836)
!    -49834 | (37595, 49849),(37581, 49834)
!    -49830 | (46151, 49848),(46058, 49830)
!    -49818 | (29261, 49910),(29247, 49818)
! (15 rows)
! 
! SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
!  ?column? |               c               
! ----------+-------------------------------
!   -100000 | (0, 100000)
!    -50073 | (36311, 50073),(36258, 49987)
!    -50040 | (30746, 50040),(30727, 49992)
!    -50012 | (2168, 50012),(2108, 49914)
!    -49983 | (21551, 49983),(21492, 49885)
!    -49975 | (17954, 49975),(17865, 49915)
!    -49962 | (3531, 49962),(3463, 49934)
!    -49932 | (19128, 49932),(19112, 49849)
!    -49923 | (31287, 49923),(31236, 49913)
!    -49912 | (43925, 49912),(43888, 49878)
!    -49910 | (29261, 49910),(29247, 49818)
!    -49873 | (14913, 49873),(14849, 49836)
!    -49858 | (20007, 49858),(19921, 49778)
!    -49852 | (38266, 49852),(38233, 49844)
!    -49849 | (37595, 49849),(37581, 49834)
  (15 rows)
  
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
new file mode 100644
index 58ea3ad..0b3e77d
*** a/contrib/cube/sql/cube.sql
--- b/contrib/cube/sql/cube.sql
*************** SELECT * FROM test_cube WHERE c && '(300
*** 382,401 ****
  -- Test sorting
  SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c;
  
! -- kNN with index
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
  
! -- kNN-based sorting
! SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
! SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
! SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
! SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
  
! -- same thing for index with points
! CREATE TABLE test_point(c cube);
! INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
! CREATE INDEX ON test_point USING gist(c);
! SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
! SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
--- 382,412 ----
  -- Test sorting
  SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c;
  
! -- Test kNN
! INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
! SET enable_seqscan = OFF;
! SET enable_indexscan = ON;
! 
! -- Test different metrics
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
  
! -- Test sorting by coordinates
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
! SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
! SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
  
! -- Same queries with sequential scan (should give the same results as above)
! SET enable_seqscan = ON;
! SET enable_indexscan = OFF;
! 
! SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
! SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
! SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
! 
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
! SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
! SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
diff --git a/doc/src/sgml/cube.sgml b/doc/src/sgml/cube.sgml
new file mode 100644
index 46d8e4e..3935ff6
*** a/doc/src/sgml/cube.sgml
--- b/doc/src/sgml/cube.sgml
***************
*** 186,195 ****
        <entry><literal>a ~&gt; n</literal></entry>
        <entry><type>float8</type></entry>
        <entry>
!         Get <replaceable>n</replaceable>-th coordinate in <quote>normalized</quote> cube
!         representation, in which the coordinates have been rearranged into
!         the form <quote>lower left &mdash; upper right</quote>; that is, the
!         smaller endpoint along each dimension appears first.
        </entry>
       </row>
  
--- 186,196 ----
        <entry><literal>a ~&gt; n</literal></entry>
        <entry><type>float8</type></entry>
        <entry>
!         Get <replaceable>n</replaceable>-th coordinate of cube in following way:
!         n = 2 * k - 1 means lower bound of <replaceable>k</replaceable>-th
!         dimension, n = 2 * k means upper bound of
!         <replaceable>k</replaceable>-th dimension.  This operator is designed
!         for KNN-GiST support.
        </entry>
       </row>
  
0002-cube-knn-negative-coordinate.patchapplication/octet-stream; name=0002-cube-knn-negative-coordinate.patchDownload
diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
new file mode 100644
index 2b10cd4..f737c35
*** a/contrib/cube/cube.c
--- b/contrib/cube/cube.c
*************** g_cube_distance(PG_FUNCTION_ARGS)
*** 1343,1354 ****
  		 */
  		int			coord = PG_GETARG_INT32(1);
  		bool		isLeaf = GistPageIsLeaf(entry->page);
  
  		/* 0 is the only unsupported coordinate value */
! 		if (coord <= 0)
  			ereport(ERROR,
  					(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
! 					 errmsg("cube index %d is out of bounds", coord)));
  
  		if (coord <= 2 * DIM(cube))
  		{
--- 1343,1362 ----
  		 */
  		int			coord = PG_GETARG_INT32(1);
  		bool		isLeaf = GistPageIsLeaf(entry->page);
+ 		bool		inverse = false;
  
  		/* 0 is the only unsupported coordinate value */
! 		if (coord == 0)
  			ereport(ERROR,
  					(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
! 					 errmsg("zero cube index is not defined")));
! 
! 		/* Return inversed value for negative coordinate */
! 		if (coord < 0)
! 		{
! 			coord = -coord;
! 			inverse = true;
! 		}
  
  		if (coord <= 2 * DIM(cube))
  		{
*************** g_cube_distance(PG_FUNCTION_ARGS)
*** 1376,1384 ****
  					/*
  					 * For non-leaf we should always return lower bound,
  					 * because even upper bound of a child in the subtree can
! 					 * be as small as our lower bound.
  					 */
! 					retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
  				}
  			}
  		}
--- 1384,1397 ----
  					/*
  					 * For non-leaf we should always return lower bound,
  					 * because even upper bound of a child in the subtree can
! 					 * be as small as our lower bound.  For inversed case we
! 					 * return upper bound because it becomes lower bound for
! 					 * inversed value.
  					 */
! 					if (!inverse)
! 						retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
! 					else
! 						retval = Max(cube->x[index], cube->x[index + DIM(cube)]);
  				}
  			}
  		}
*************** g_cube_distance(PG_FUNCTION_ARGS)
*** 1386,1391 ****
--- 1399,1408 ----
  		{
  			retval = 0.0;
  		}
+ 
+ 		/* Inverse return value if needed */
+ 		if (inverse)
+ 			retval = -retval;
  	}
  	else
  	{
*************** cube_coord(PG_FUNCTION_ARGS)
*** 1542,1552 ****
   * getter.  Moreover, indexed dataset may contain cubes of different dimensions
   * number.  Accordingly, this corrdinate getter should be able to return
   * lower/upper bound for particular dimension independently on number of cube
!  * dimenstions.
   *
   * Long stoty short, this function uses following meaning of coordinates:
   * # (2 * N - 1) -- lower bound of Nth dimension,
!  * # (2 * N) -- upper bound of Nth dimenstion.
   *
   * When given coordinate exceeds number of cube dimensions, then 0 returned
   * (reproducing logic of GiST indexing of variable-length cubes).
--- 1559,1573 ----
   * getter.  Moreover, indexed dataset may contain cubes of different dimensions
   * number.  Accordingly, this corrdinate getter should be able to return
   * lower/upper bound for particular dimension independently on number of cube
!  * dimenstions.  Also, KNN-GiST supports only ascending sorting.  In order to
!  * support descinding sorting, this function returns inverse of value when
!  * negative coordinate is given.
   *
   * Long stoty short, this function uses following meaning of coordinates:
   * # (2 * N - 1) -- lower bound of Nth dimension,
!  * # (2 * N) -- upper bound of Nth dimenstion,
!  * # - (2 * N - 1) -- negative of lower bound of Nth dimension,
!  * # - (2 * N) -- negative of upper bound of Nth dimenstion.
   *
   * When given coordinate exceeds number of cube dimensions, then 0 returned
   * (reproducing logic of GiST indexing of variable-length cubes).
*************** cube_coord_llur(PG_FUNCTION_ARGS)
*** 1560,1569 ****
  	float8		result;
  
  	/* 0 is the only unsupported coordinate value */
! 	if (coord <= 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
! 				 errmsg("cube index %d is out of bounds", coord)));
  
  	if (coord <= 2 * DIM(cube))
  	{
--- 1581,1597 ----
  	float8		result;
  
  	/* 0 is the only unsupported coordinate value */
! 	if (coord == 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
! 				 errmsg("zero cube index is not defined")));
! 
! 	/* Return inversed value for negative coordinate */
! 	if (coord < 0)
! 	{
! 		coord = -coord;
! 		inverse = true;
! 	}
  
  	if (coord <= 2 * DIM(cube))
  	{
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
new file mode 100644
index 8f85fc0..b6a694e
*** a/contrib/cube/expected/cube.out
--- b/contrib/cube/expected/cube.out
*************** SELECT cube(array[40,50,60], array[10,20
*** 1554,1560 ****
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>0;
! ERROR:  cube index 0 is out of bounds
  SELECT cube(array[40,50,60], array[10,20,30])~>4;
   ?column? 
  ----------
--- 1554,1560 ----
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>0;
! ERROR:  zero cube index is not defined
  SELECT cube(array[40,50,60], array[10,20,30])~>4;
   ?column? 
  ----------
*************** SELECT cube(array[40,50,60], array[10,20
*** 1562,1568 ****
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
! ERROR:  cube index -1 is out of bounds
  -- Load some example data and build the index
  --
  CREATE TABLE test_cube (c cube);
--- 1562,1572 ----
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
!  ?column? 
! ----------
!       -10
! (1 row)
! 
  -- Load some example data and build the index
  --
  CREATE TABLE test_cube (c cube);
*************** SELECT c~>4, c FROM test_cube ORDER BY c
*** 1705,1710 ****
--- 1709,1794 ----
        266 | (22684, 266),(22656, 181)
  (15 rows)
  
+ SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -49951 | (50027, 49230),(49951, 49214)
+    -49937 | (49980, 35004),(49937, 34963)
+    -49927 | (49985, 6436),(49927, 6338)
+    -49908 | (49999, 27218),(49908, 27176)
+    -49905 | (49954, 1340),(49905, 1294)
+    -49902 | (49944, 25163),(49902, 25153)
+    -49898 | (49981, 34876),(49898, 34786)
+    -49897 | (49957, 43390),(49897, 43384)
+    -49848 | (49853, 18504),(49848, 18503)
+    -49818 | (49902, 41752),(49818, 41746)
+    -49810 | (49907, 30225),(49810, 30158)
+    -49808 | (49843, 5175),(49808, 5145)
+    -49805 | (49887, 24274),(49805, 24184)
+    -49798 | (49847, 7128),(49798, 7067)
+ (15 rows)
+ 
+ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -50027 | (50027, 49230),(49951, 49214)
+    -49999 | (49999, 27218),(49908, 27176)
+    -49985 | (49985, 6436),(49927, 6338)
+    -49981 | (49981, 34876),(49898, 34786)
+    -49980 | (49980, 35004),(49937, 34963)
+    -49957 | (49957, 43390),(49897, 43384)
+    -49954 | (49954, 1340),(49905, 1294)
+    -49944 | (49944, 25163),(49902, 25153)
+    -49907 | (49907, 30225),(49810, 30158)
+    -49902 | (49902, 41752),(49818, 41746)
+    -49887 | (49887, 24274),(49805, 24184)
+    -49853 | (49853, 18504),(49848, 18503)
+    -49847 | (49847, 7128),(49798, 7067)
+    -49843 | (49843, 5175),(49808, 5145)
+ (15 rows)
+ 
+ SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -49992 | (30746, 50040),(30727, 49992)
+    -49987 | (36311, 50073),(36258, 49987)
+    -49934 | (3531, 49962),(3463, 49934)
+    -49915 | (17954, 49975),(17865, 49915)
+    -49914 | (2168, 50012),(2108, 49914)
+    -49913 | (31287, 49923),(31236, 49913)
+    -49885 | (21551, 49983),(21492, 49885)
+    -49878 | (43925, 49912),(43888, 49878)
+    -49849 | (19128, 49932),(19112, 49849)
+    -49844 | (38266, 49852),(38233, 49844)
+    -49836 | (14913, 49873),(14849, 49836)
+    -49834 | (37595, 49849),(37581, 49834)
+    -49830 | (46151, 49848),(46058, 49830)
+    -49818 | (29261, 49910),(29247, 49818)
+ (15 rows)
+ 
+ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -50073 | (36311, 50073),(36258, 49987)
+    -50040 | (30746, 50040),(30727, 49992)
+    -50012 | (2168, 50012),(2108, 49914)
+    -49983 | (21551, 49983),(21492, 49885)
+    -49975 | (17954, 49975),(17865, 49915)
+    -49962 | (3531, 49962),(3463, 49934)
+    -49932 | (19128, 49932),(19112, 49849)
+    -49923 | (31287, 49923),(31236, 49913)
+    -49912 | (43925, 49912),(43888, 49878)
+    -49910 | (29261, 49910),(29247, 49818)
+    -49873 | (14913, 49873),(14849, 49836)
+    -49858 | (20007, 49858),(19921, 49778)
+    -49852 | (38266, 49852),(38233, 49844)
+    -49849 | (37595, 49849),(37581, 49834)
+ (15 rows)
+ 
  -- Same queries with sequential scan (should give the same results as above)
  SET enable_seqscan = ON;
  SET enable_indexscan = OFF;
*************** SELECT c~>4, c FROM test_cube ORDER BY c
*** 1818,1820 ****
--- 1902,1984 ----
        266 | (22684, 266),(22656, 181)
  (15 rows)
  
+ SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -49951 | (50027, 49230),(49951, 49214)
+    -49937 | (49980, 35004),(49937, 34963)
+    -49927 | (49985, 6436),(49927, 6338)
+    -49908 | (49999, 27218),(49908, 27176)
+    -49905 | (49954, 1340),(49905, 1294)
+    -49902 | (49944, 25163),(49902, 25153)
+    -49898 | (49981, 34876),(49898, 34786)
+    -49897 | (49957, 43390),(49897, 43384)
+    -49848 | (49853, 18504),(49848, 18503)
+    -49818 | (49902, 41752),(49818, 41746)
+    -49810 | (49907, 30225),(49810, 30158)
+    -49808 | (49843, 5175),(49808, 5145)
+    -49805 | (49887, 24274),(49805, 24184)
+    -49798 | (49847, 7128),(49798, 7067)
+ (15 rows)
+ 
+ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -50027 | (50027, 49230),(49951, 49214)
+    -49999 | (49999, 27218),(49908, 27176)
+    -49985 | (49985, 6436),(49927, 6338)
+    -49981 | (49981, 34876),(49898, 34786)
+    -49980 | (49980, 35004),(49937, 34963)
+    -49957 | (49957, 43390),(49897, 43384)
+    -49954 | (49954, 1340),(49905, 1294)
+    -49944 | (49944, 25163),(49902, 25153)
+    -49907 | (49907, 30225),(49810, 30158)
+    -49902 | (49902, 41752),(49818, 41746)
+    -49887 | (49887, 24274),(49805, 24184)
+    -49853 | (49853, 18504),(49848, 18503)
+    -49847 | (49847, 7128),(49798, 7067)
+    -49843 | (49843, 5175),(49808, 5145)
+ (15 rows)
+ 
+ SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -49992 | (30746, 50040),(30727, 49992)
+    -49987 | (36311, 50073),(36258, 49987)
+    -49934 | (3531, 49962),(3463, 49934)
+    -49915 | (17954, 49975),(17865, 49915)
+    -49914 | (2168, 50012),(2108, 49914)
+    -49913 | (31287, 49923),(31236, 49913)
+    -49885 | (21551, 49983),(21492, 49885)
+    -49878 | (43925, 49912),(43888, 49878)
+    -49849 | (19128, 49932),(19112, 49849)
+    -49844 | (38266, 49852),(38233, 49844)
+    -49836 | (14913, 49873),(14849, 49836)
+    -49834 | (37595, 49849),(37581, 49834)
+    -49830 | (46151, 49848),(46058, 49830)
+    -49818 | (29261, 49910),(29247, 49818)
+ (15 rows)
+ 
+ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -50073 | (36311, 50073),(36258, 49987)
+    -50040 | (30746, 50040),(30727, 49992)
+    -50012 | (2168, 50012),(2108, 49914)
+    -49983 | (21551, 49983),(21492, 49885)
+    -49975 | (17954, 49975),(17865, 49915)
+    -49962 | (3531, 49962),(3463, 49934)
+    -49932 | (19128, 49932),(19112, 49849)
+    -49923 | (31287, 49923),(31236, 49913)
+    -49912 | (43925, 49912),(43888, 49878)
+    -49910 | (29261, 49910),(29247, 49818)
+    -49873 | (14913, 49873),(14849, 49836)
+    -49858 | (20007, 49858),(19921, 49778)
+    -49852 | (38266, 49852),(38233, 49844)
+    -49849 | (37595, 49849),(37581, 49834)
+ (15 rows)
+ 
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
new file mode 100644
index 0b3e77d..510d5a7
*** a/contrib/cube/sql/cube.sql
--- b/contrib/cube/sql/cube.sql
*************** SELECT c~>1, c FROM test_cube ORDER BY c
*** 397,402 ****
--- 397,406 ----
  SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
  SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
  SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+ SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
  
  -- Same queries with sequential scan (should give the same results as above)
  SET enable_seqscan = ON;
*************** SELECT c~>1, c FROM test_cube ORDER BY c
*** 410,412 ****
--- 414,420 ----
  SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
  SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
  SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+ SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
diff --git a/doc/src/sgml/cube.sgml b/doc/src/sgml/cube.sgml
new file mode 100644
index 3935ff6..65887b1
*** a/doc/src/sgml/cube.sgml
--- b/doc/src/sgml/cube.sgml
***************
*** 189,196 ****
          Get <replaceable>n</replaceable>-th coordinate of cube in following way:
          n = 2 * k - 1 means lower bound of <replaceable>k</replaceable>-th
          dimension, n = 2 * k means upper bound of
!         <replaceable>k</replaceable>-th dimension.  This operator is designed
!         for KNN-GiST support.
        </entry>
       </row>
  
--- 189,197 ----
          Get <replaceable>n</replaceable>-th coordinate of cube in following way:
          n = 2 * k - 1 means lower bound of <replaceable>k</replaceable>-th
          dimension, n = 2 * k means upper bound of
!         <replaceable>k</replaceable>-th dimension.  Negative
!         <replaceable>n</replaceable> denotes inversed value of corresponding
!         positive coordinate.  This operator is designed for KNN-GiST support.
        </entry>
       </row>
  
#8Michael Paquier
michael.paquier@gmail.com
In reply to: Alexander Korotkov (#7)
Re: [HACKERS] CUBE seems a bit confused about ORDER BY

On Tue, Nov 21, 2017 at 7:07 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Mon, Nov 20, 2017 at 1:59 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Mon, Nov 20, 2017 at 4:09 AM, Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

Seems fine to me, although perhaps it should be split into two parts.
One with the cube_coord_llur fixes, and then g_cube_distance changes
adding support for negative coordinates.

Thank you for your feedback. I'll split this patch into two.

Please, find two patches attached.

This got no reviews, so moved to next CF.
--
Michael

#9Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Michael Paquier (#8)
Re: [HACKERS] CUBE seems a bit confused about ORDER BY

On 11/29/2017 06:13 AM, Michael Paquier wrote:

On Tue, Nov 21, 2017 at 7:07 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Mon, Nov 20, 2017 at 1:59 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Mon, Nov 20, 2017 at 4:09 AM, Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

Seems fine to me, although perhaps it should be split into two parts.
One with the cube_coord_llur fixes, and then g_cube_distance changes
adding support for negative coordinates.

Thank you for your feedback. I'll split this patch into two.

Please, find two patches attached.

This got no reviews, so moved to next CF.

That is somewhat inaccurate, I believe. I won't claim it received enough
reviews, but it certainly received some. And splitting it into two does
not invalidate that, I guess.

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#10Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Tomas Vondra (#9)
Re: [HACKERS] CUBE seems a bit confused about ORDER BY

On Wed, Nov 29, 2017 at 1:30 PM, Tomas Vondra <tomas.vondra@2ndquadrant.com>
wrote:

On 11/29/2017 06:13 AM, Michael Paquier wrote:

On Tue, Nov 21, 2017 at 7:07 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Mon, Nov 20, 2017 at 1:59 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

On Mon, Nov 20, 2017 at 4:09 AM, Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:

Seems fine to me, although perhaps it should be split into two parts.
One with the cube_coord_llur fixes, and then g_cube_distance changes
adding support for negative coordinates.

Thank you for your feedback. I'll split this patch into two.

Please, find two patches attached.

This got no reviews, so moved to next CF.

That is somewhat inaccurate, I believe. I won't claim it received enough
reviews, but it certainly received some. And splitting it into two does
not invalidate that, I guess.

Sure, patch got some review. I've no objection against moving this to the
next commitfest though.
Since, these patches include bug fix, it's possible that someone will
commit it before next commitfest.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#11Andrey Borodin
x4mmm@yandex-team.ru
In reply to: Alexander Korotkov (#10)
Re: [HACKERS] CUBE seems a bit confused about ORDER BY

29 нояб. 2017 г., в 15:59, Alexander Korotkov <a.korotkov@postgrespro.ru> написал(а):

Sure, patch got some review. I've no objection against moving this to the next commitfest though.
Since, these patches include bug fix, it's possible that someone will commit it before next commitfest.

Hi!

I've took a glance at the patch, here's what catches my eye in comments: corrdinate, dimenstions, descinding, stoty.

I'll try to provide meaningful review next week.

Best regards, Andrey Borodin.

#12Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Andrey Borodin (#11)
2 attachment(s)
Re: [HACKERS] CUBE seems a bit confused about ORDER BY

Hi!

On Wed, Nov 29, 2017 at 2:32 PM, Andrey Borodin <x4mmm@yandex-team.ru>
wrote:

29 нояб. 2017 г., в 15:59, Alexander Korotkov <a.korotkov@postgrespro.ru>
написал(а):

Sure, patch got some review. I've no objection against moving this to the
next commitfest though.
Since, these patches include bug fix, it's possible that someone will
commit it before next commitfest.

I've took a glance at the patch, here's what catches my eye in
comments: corrdinate, dimenstions, descinding, stoty.

Thank you for catching these typos. Rebased patchset with fixes typos is
attached.

I'll try to provide meaningful review next week.

Cool, thanks.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-cube-knn-fix-2.patchapplication/octet-stream; name=0001-cube-knn-fix-2.patchDownload
diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
new file mode 100644
index 3e3d833..dcc0850
*** a/contrib/cube/cube.c
--- b/contrib/cube/cube.c
*************** g_cube_distance(PG_FUNCTION_ARGS)
*** 1337,1351 ****
  
  	if (strategy == CubeKNNDistanceCoord)
  	{
  		int			coord = PG_GETARG_INT32(1);
  
! 		if (DIM(cube) == 0)
! 			retval = 0.0;
! 		else if (IS_POINT(cube))
! 			retval = cube->x[(coord - 1) % DIM(cube)];
  		else
! 			retval = Min(cube->x[(coord - 1) % DIM(cube)],
! 						 cube->x[(coord - 1) % DIM(cube) + DIM(cube)]);
  	}
  	else
  	{
--- 1337,1391 ----
  
  	if (strategy == CubeKNNDistanceCoord)
  	{
+ 		/*
+ 		 * Handle ordering by ~> operator.  See comments of cube_coord_llur()
+ 		 * for details
+ 		 */
  		int			coord = PG_GETARG_INT32(1);
+ 		bool		isLeaf = GistPageIsLeaf(entry->page);
  
! 		/* 0 is the only unsupported coordinate value */
! 		if (coord <= 0)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
! 					 errmsg("cube index %d is out of bounds", coord)));
! 
! 		if (coord <= 2 * DIM(cube))
! 		{
! 			/* dimension index */
! 			int		index = (coord - 1) / 2;
! 			/* whether this is upper bound (lower bound otherwise) */
! 			bool	upper = ((coord - 1) % 2 == 1);
! 
! 			if (IS_POINT(cube))
! 			{
! 				retval = cube->x[index];
! 			}
! 			else
! 			{
! 				if (isLeaf)
! 				{
! 					/* For leaf just return required upper/lower bound */
! 					if (upper)
! 						retval = Max(cube->x[index], cube->x[index + DIM(cube)]);
! 					else
! 						retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
! 				}
! 				else
! 				{
! 					/*
! 					 * For non-leaf we should always return lower bound,
! 					 * because even upper bound of a child in the subtree can
! 					 * be as small as our lower bound.
! 					 */
! 					retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
! 				}
! 			}
! 		}
  		else
! 		{
! 			retval = 0.0;
! 		}
  	}
  	else
  	{
*************** cube_coord(PG_FUNCTION_ARGS)
*** 1492,1534 ****
  }
  
  
! /*
!  * This function works like cube_coord(),
!  * but rearranges coordinates of corners to get cube representation
!  * in the form of (lower left, upper right).
!  * For historical reasons that extension allows us to create cubes in form
!  * ((2,1),(1,2)) and instead of normalizing such cube to ((1,1),(2,2)) it
!  * stores cube in original way. But to get cubes ordered by one of dimensions
!  * directly from the index without extra sort step we need some
!  * representation-independent coordinate getter. This function implements it.
   */
  Datum
  cube_coord_llur(PG_FUNCTION_ARGS)
  {
  	NDBOX	   *cube = PG_GETARG_NDBOX_P(0);
  	int			coord = PG_GETARG_INT32(1);
  
! 	if (coord <= 0 || coord > 2 * DIM(cube))
  		ereport(ERROR,
  				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
  				 errmsg("cube index %d is out of bounds", coord)));
  
! 	if (coord <= DIM(cube))
  	{
  		if (IS_POINT(cube))
! 			PG_RETURN_FLOAT8(cube->x[coord - 1]);
  		else
! 			PG_RETURN_FLOAT8(Min(cube->x[coord - 1],
! 								 cube->x[coord - 1 + DIM(cube)]));
  	}
  	else
  	{
! 		if (IS_POINT(cube))
! 			PG_RETURN_FLOAT8(cube->x[(coord - 1) % DIM(cube)]);
! 		else
! 			PG_RETURN_FLOAT8(Max(cube->x[coord - 1],
! 								 cube->x[coord - 1 - DIM(cube)]));
  	}
  }
  
  /* Increase or decrease box size by a radius in at least n dimensions. */
--- 1532,1604 ----
  }
  
  
! /*----
!  * This function works like cube_coord(), but rearranges coordinates in the
!  * way suitable to support coordinate ordering using KNN-GiST.  For historical
!  * reasons this extension allows us to create cubes in form ((2,1),(1,2)) and
!  * instead of normalizing such cube to ((1,1),(2,2)) it stores cube in original
!  * way.  But in order to get cubes ordered by one of dimensions from the index
!  * without explicit sort step we need this representation-independent coordinate
!  * getter.  Moreover, indexed dataset may contain cubes of different dimensions
!  * number.  Accordingly, this coordinate getter should be able to return
!  * lower/upper bound for particular dimension independently on number of cube
!  * dimensions.
!  *
!  * Long story short, this function uses following meaning of coordinates:
!  * # (2 * N - 1) -- lower bound of Nth dimension,
!  * # (2 * N) -- upper bound of Nth dimension.
!  *
!  * When given coordinate exceeds number of cube dimensions, then 0 returned
!  * (reproducing logic of GiST indexing of variable-length cubes).
   */
  Datum
  cube_coord_llur(PG_FUNCTION_ARGS)
  {
  	NDBOX	   *cube = PG_GETARG_NDBOX_P(0);
  	int			coord = PG_GETARG_INT32(1);
+ 	bool		inverse = false;
+ 	float8		result;
  
! 	/* 0 is the only unsupported coordinate value */
! 	if (coord <= 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
  				 errmsg("cube index %d is out of bounds", coord)));
  
! 	if (coord <= 2 * DIM(cube))
  	{
+ 		/* dimension index */
+ 		int		index = (coord - 1) / 2;
+ 		/* whether this is upper bound (lower bound otherwise) */
+ 		bool	upper = ((coord - 1) % 2 == 1);
+ 
  		if (IS_POINT(cube))
! 		{
! 			result = cube->x[index];
! 		}
  		else
! 		{
! 			if (upper)
! 				result = Max(cube->x[index], cube->x[index + DIM(cube)]);
! 			else
! 				result = Min(cube->x[index], cube->x[index + DIM(cube)]);
! 		}
  	}
  	else
  	{
! 		/*
! 		 * Return zero if coordinate is out of bound.  That reproduces logic of
! 		 * how cubes with low dimension number are expanded during GiST
! 		 * indexing.
! 		 */
! 		result = 0.0;
  	}
+ 
+ 	/* Inverse value if needed */
+ 	if (inverse)
+ 		result = -result;
+ 
+ 	PG_RETURN_FLOAT8(result);
  }
  
  /* Increase or decrease box size by a radius in at least n dimensions. */
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
new file mode 100644
index c430b4e..c586a73
*** a/contrib/cube/expected/cube.out
--- b/contrib/cube/expected/cube.out
*************** SELECT cube(array[40,50,60], array[10,20
*** 1532,1556 ****
  SELECT cube(array[10,20,30], array[40,50,60])~>2;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>2;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[10,20,30], array[40,50,60])~>3;
   ?column? 
  ----------
!        30
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>3;
   ?column? 
  ----------
!        30
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>0;
--- 1532,1556 ----
  SELECT cube(array[10,20,30], array[40,50,60])~>2;
   ?column? 
  ----------
!        40
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>2;
   ?column? 
  ----------
!        40
  (1 row)
  
  SELECT cube(array[10,20,30], array[40,50,60])~>3;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>3;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>0;
*************** ERROR:  cube index 0 is out of bounds
*** 1558,1564 ****
  SELECT cube(array[40,50,60], array[10,20,30])~>4;
   ?column? 
  ----------
!        40
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
--- 1558,1564 ----
  SELECT cube(array[40,50,60], array[10,20,30])~>4;
   ?column? 
  ----------
!        50
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
*************** SELECT c FROM test_cube WHERE c <@ '(300
*** 1611,1635 ****
  (4 rows)
  
  RESET enable_bitmapscan;
! -- kNN with index
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            |       dist       
  -------------------------+------------------
   (337, 455),(240, 359)   |                0
   (759, 187),(662, 163)   |              162
   (948, 1201),(907, 1156) | 772.000647668122
   (1444, 403),(1346, 344) |              846
-  (369, 1457),(278, 1409) |              909
  (5 rows)
  
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
   (948, 1201),(907, 1156) |  656
   (1444, 403),(1346, 344) |  846
-  (369, 1457),(278, 1409) |  909
  (5 rows)
  
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
--- 1611,1638 ----
  (4 rows)
  
  RESET enable_bitmapscan;
! -- Test kNN
! INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
! SET enable_seqscan = false;
! -- Test different metrics
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            |       dist       
  -------------------------+------------------
   (337, 455),(240, 359)   |                0
+  (1, 1)                  | 140.007142674936
   (759, 187),(662, 163)   |              162
   (948, 1201),(907, 1156) | 772.000647668122
   (1444, 403),(1346, 344) |              846
  (5 rows)
  
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
+  (1, 1)                  |   99
   (759, 187),(662, 163)   |  162
   (948, 1201),(907, 1156) |  656
   (1444, 403),(1346, 344) |  846
  (5 rows)
  
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
*************** SELECT *, c <#> '(100, 100),(500, 500)':
*** 1637,1769 ****
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
-  (948, 1201),(907, 1156) | 1063
  (5 rows)
  
! -- kNN-based sorting
! SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
!              c             
! ---------------------------
!  (54, 38679),(3, 38602)
!  (83, 10271),(15, 10265)
!  (122, 46832),(64, 46762)
!  (167, 17214),(92, 17184)
!  (161, 24465),(107, 24374)
!  (162, 26040),(120, 25963)
!  (154, 4019),(138, 3990)
!  (259, 1850),(175, 1820)
!  (207, 40886),(179, 40879)
!  (288, 49588),(204, 49571)
!  (270, 32616),(226, 32607)
!  (318, 31489),(235, 31404)
!  (337, 455),(240, 359)
!  (270, 29508),(264, 29440)
!  (369, 1457),(278, 1409)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
!              c             
! ---------------------------
!  (30333, 50),(30273, 6)
!  (43301, 75),(43227, 43)
!  (19650, 142),(19630, 51)
!  (2424, 160),(2424, 81)
!  (3449, 171),(3354, 108)
!  (18037, 155),(17941, 109)
!  (28511, 208),(28479, 114)
!  (19946, 217),(19941, 118)
!  (16906, 191),(16816, 139)
!  (759, 187),(662, 163)
!  (22684, 266),(22656, 181)
!  (24423, 255),(24360, 213)
!  (45989, 249),(45910, 222)
!  (11399, 377),(11360, 294)
!  (12162, 389),(12103, 309)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
!                c               
! -------------------------------
!  (50027, 49230),(49951, 49214)
!  (49980, 35004),(49937, 34963)
!  (49985, 6436),(49927, 6338)
!  (49999, 27218),(49908, 27176)
!  (49954, 1340),(49905, 1294)
!  (49944, 25163),(49902, 25153)
!  (49981, 34876),(49898, 34786)
!  (49957, 43390),(49897, 43384)
!  (49853, 18504),(49848, 18503)
!  (49902, 41752),(49818, 41746)
!  (49907, 30225),(49810, 30158)
!  (49843, 5175),(49808, 5145)
!  (49887, 24274),(49805, 24184)
!  (49847, 7128),(49798, 7067)
!  (49820, 7990),(49771, 7967)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
!                c               
! -------------------------------
!  (36311, 50073),(36258, 49987)
!  (30746, 50040),(30727, 49992)
!  (2168, 50012),(2108, 49914)
!  (21551, 49983),(21492, 49885)
!  (17954, 49975),(17865, 49915)
!  (3531, 49962),(3463, 49934)
!  (19128, 49932),(19112, 49849)
!  (31287, 49923),(31236, 49913)
!  (43925, 49912),(43888, 49878)
!  (29261, 49910),(29247, 49818)
!  (14913, 49873),(14849, 49836)
!  (20007, 49858),(19921, 49778)
!  (38266, 49852),(38233, 49844)
!  (37595, 49849),(37581, 49834)
!  (46151, 49848),(46058, 49830)
  (15 rows)
  
! -- same thing for index with points
! CREATE TABLE test_point(c cube);
! INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
! CREATE INDEX ON test_point USING gist(c);
! SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
!             c             
! --------------------------
!  (54, 38679, 3, 38602)
!  (83, 10271, 15, 10265)
!  (122, 46832, 64, 46762)
!  (154, 4019, 138, 3990)
!  (161, 24465, 107, 24374)
!  (162, 26040, 120, 25963)
!  (167, 17214, 92, 17184)
!  (207, 40886, 179, 40879)
!  (259, 1850, 175, 1820)
!  (270, 29508, 264, 29440)
!  (270, 32616, 226, 32607)
!  (288, 49588, 204, 49571)
!  (318, 31489, 235, 31404)
!  (326, 18837, 285, 18817)
!  (337, 455, 240, 359)
  (15 rows)
  
! SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
!               c               
! ------------------------------
!  (30746, 50040, 30727, 49992)
!  (36311, 50073, 36258, 49987)
!  (3531, 49962, 3463, 49934)
!  (17954, 49975, 17865, 49915)
!  (2168, 50012, 2108, 49914)
!  (31287, 49923, 31236, 49913)
!  (21551, 49983, 21492, 49885)
!  (43925, 49912, 43888, 49878)
!  (19128, 49932, 19112, 49849)
!  (38266, 49852, 38233, 49844)
!  (14913, 49873, 14849, 49836)
!  (37595, 49849, 37581, 49834)
!  (46151, 49848, 46058, 49830)
!  (29261, 49910, 29247, 49818)
!  (19233, 49824, 19185, 49794)
  (15 rows)
  
--- 1640,1842 ----
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
+  (1, 1)                  |  198
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
  (5 rows)
  
! -- Test sorting by coordinates
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!         3 | (54, 38679),(3, 38602)
!        15 | (83, 10271),(15, 10265)
!        64 | (122, 46832),(64, 46762)
!        92 | (167, 17214),(92, 17184)
!       107 | (161, 24465),(107, 24374)
!       120 | (162, 26040),(120, 25963)
!       138 | (154, 4019),(138, 3990)
!       175 | (259, 1850),(175, 1820)
!       179 | (207, 40886),(179, 40879)
!       204 | (288, 49588),(204, 49571)
!       226 | (270, 32616),(226, 32607)
!       235 | (318, 31489),(235, 31404)
!       240 | (337, 455),(240, 359)
  (15 rows)
  
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!        54 | (54, 38679),(3, 38602)
!        83 | (83, 10271),(15, 10265)
!       122 | (122, 46832),(64, 46762)
!       154 | (154, 4019),(138, 3990)
!       161 | (161, 24465),(107, 24374)
!       162 | (162, 26040),(120, 25963)
!       167 | (167, 17214),(92, 17184)
!       207 | (207, 40886),(179, 40879)
!       259 | (259, 1850),(175, 1820)
!       270 | (270, 29508),(264, 29440)
!       270 | (270, 32616),(226, 32607)
!       288 | (288, 49588),(204, 49571)
!       318 | (318, 31489),(235, 31404)
  (15 rows)
  
! SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (100000)
!         1 | (1, 1)
!         6 | (30333, 50),(30273, 6)
!        43 | (43301, 75),(43227, 43)
!        51 | (19650, 142),(19630, 51)
!        81 | (2424, 160),(2424, 81)
!       108 | (3449, 171),(3354, 108)
!       109 | (18037, 155),(17941, 109)
!       114 | (28511, 208),(28479, 114)
!       118 | (19946, 217),(19941, 118)
!       139 | (16906, 191),(16816, 139)
!       163 | (759, 187),(662, 163)
!       181 | (22684, 266),(22656, 181)
!       213 | (24423, 255),(24360, 213)
!       222 | (45989, 249),(45910, 222)
  (15 rows)
  
! SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (100000)
!         1 | (1, 1)
!        50 | (30333, 50),(30273, 6)
!        75 | (43301, 75),(43227, 43)
!       142 | (19650, 142),(19630, 51)
!       155 | (18037, 155),(17941, 109)
!       160 | (2424, 160),(2424, 81)
!       171 | (3449, 171),(3354, 108)
!       187 | (759, 187),(662, 163)
!       191 | (16906, 191),(16816, 139)
!       208 | (28511, 208),(28479, 114)
!       217 | (19946, 217),(19941, 118)
!       249 | (45989, 249),(45910, 222)
!       255 | (24423, 255),(24360, 213)
!       266 | (22684, 266),(22656, 181)
  (15 rows)
  
! -- Same queries with sequential scan (should give the same results as above)
! RESET enable_seqscan;
! SET enable_indexscan = OFF;
! SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
!             c            |       dist       
! -------------------------+------------------
!  (337, 455),(240, 359)   |                0
!  (1, 1)                  | 140.007142674936
!  (759, 187),(662, 163)   |              162
!  (948, 1201),(907, 1156) | 772.000647668122
!  (1444, 403),(1346, 344) |              846
! (5 rows)
! 
! SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
!             c            | dist 
! -------------------------+------
!  (337, 455),(240, 359)   |    0
!  (1, 1)                  |   99
!  (759, 187),(662, 163)   |  162
!  (948, 1201),(907, 1156) |  656
!  (1444, 403),(1346, 344) |  846
! (5 rows)
! 
! SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
!             c            | dist 
! -------------------------+------
!  (337, 455),(240, 359)   |    0
!  (759, 187),(662, 163)   |  162
!  (1, 1)                  |  198
!  (1444, 403),(1346, 344) |  846
!  (369, 1457),(278, 1409) |  909
! (5 rows)
! 
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!         3 | (54, 38679),(3, 38602)
!        15 | (83, 10271),(15, 10265)
!        64 | (122, 46832),(64, 46762)
!        92 | (167, 17214),(92, 17184)
!       107 | (161, 24465),(107, 24374)
!       120 | (162, 26040),(120, 25963)
!       138 | (154, 4019),(138, 3990)
!       175 | (259, 1850),(175, 1820)
!       179 | (207, 40886),(179, 40879)
!       204 | (288, 49588),(204, 49571)
!       226 | (270, 32616),(226, 32607)
!       235 | (318, 31489),(235, 31404)
!       240 | (337, 455),(240, 359)
  (15 rows)
  
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!        54 | (54, 38679),(3, 38602)
!        83 | (83, 10271),(15, 10265)
!       122 | (122, 46832),(64, 46762)
!       154 | (154, 4019),(138, 3990)
!       161 | (161, 24465),(107, 24374)
!       162 | (162, 26040),(120, 25963)
!       167 | (167, 17214),(92, 17184)
!       207 | (207, 40886),(179, 40879)
!       259 | (259, 1850),(175, 1820)
!       270 | (270, 29508),(264, 29440)
!       270 | (270, 32616),(226, 32607)
!       288 | (288, 49588),(204, 49571)
!       318 | (318, 31489),(235, 31404)
  (15 rows)
  
+ SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (100000)
+         1 | (1, 1)
+         6 | (30333, 50),(30273, 6)
+        43 | (43301, 75),(43227, 43)
+        51 | (19650, 142),(19630, 51)
+        81 | (2424, 160),(2424, 81)
+       108 | (3449, 171),(3354, 108)
+       109 | (18037, 155),(17941, 109)
+       114 | (28511, 208),(28479, 114)
+       118 | (19946, 217),(19941, 118)
+       139 | (16906, 191),(16816, 139)
+       163 | (759, 187),(662, 163)
+       181 | (22684, 266),(22656, 181)
+       213 | (24423, 255),(24360, 213)
+       222 | (45989, 249),(45910, 222)
+ (15 rows)
+ 
+ SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (100000)
+         1 | (1, 1)
+        50 | (30333, 50),(30273, 6)
+        75 | (43301, 75),(43227, 43)
+       142 | (19650, 142),(19630, 51)
+       155 | (18037, 155),(17941, 109)
+       160 | (2424, 160),(2424, 81)
+       171 | (3449, 171),(3354, 108)
+       187 | (759, 187),(662, 163)
+       191 | (16906, 191),(16816, 139)
+       208 | (28511, 208),(28479, 114)
+       217 | (19946, 217),(19941, 118)
+       249 | (45989, 249),(45910, 222)
+       255 | (24423, 255),(24360, 213)
+       266 | (22684, 266),(22656, 181)
+ (15 rows)
+ 
+ RESET enable_indexscan;
diff --git a/contrib/cube/expected/cube_2.out b/contrib/cube/expected/cube_2.out
new file mode 100644
index b979c4d..8c75e27
*** a/contrib/cube/expected/cube_2.out
--- b/contrib/cube/expected/cube_2.out
*************** SELECT cube(array[40,50,60], array[10,20
*** 1532,1556 ****
  SELECT cube(array[10,20,30], array[40,50,60])~>2;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>2;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[10,20,30], array[40,50,60])~>3;
   ?column? 
  ----------
!        30
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>3;
   ?column? 
  ----------
!        30
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>0;
--- 1532,1556 ----
  SELECT cube(array[10,20,30], array[40,50,60])~>2;
   ?column? 
  ----------
!        40
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>2;
   ?column? 
  ----------
!        40
  (1 row)
  
  SELECT cube(array[10,20,30], array[40,50,60])~>3;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>3;
   ?column? 
  ----------
!        20
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>0;
*************** ERROR:  cube index 0 is out of bounds
*** 1558,1564 ****
  SELECT cube(array[40,50,60], array[10,20,30])~>4;
   ?column? 
  ----------
!        40
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
--- 1558,1564 ----
  SELECT cube(array[40,50,60], array[10,20,30])~>4;
   ?column? 
  ----------
!        50
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
*************** SELECT c FROM test_cube WHERE c <@ '(300
*** 1611,1635 ****
  (4 rows)
  
  RESET enable_bitmapscan;
! -- kNN with index
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            |       dist       
  -------------------------+------------------
   (337, 455),(240, 359)   |                0
   (759, 187),(662, 163)   |              162
   (948, 1201),(907, 1156) | 772.000647668122
   (1444, 403),(1346, 344) |              846
-  (369, 1457),(278, 1409) |              909
  (5 rows)
  
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
   (948, 1201),(907, 1156) |  656
   (1444, 403),(1346, 344) |  846
-  (369, 1457),(278, 1409) |  909
  (5 rows)
  
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
--- 1611,1638 ----
  (4 rows)
  
  RESET enable_bitmapscan;
! -- Test kNN
! INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
! SET enable_seqscan = false;
! -- Test different metrics
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            |       dist       
  -------------------------+------------------
   (337, 455),(240, 359)   |                0
+  (1, 1)                  | 140.007142674936
   (759, 187),(662, 163)   |              162
   (948, 1201),(907, 1156) | 772.000647668122
   (1444, 403),(1346, 344) |              846
  (5 rows)
  
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
              c            | dist 
  -------------------------+------
   (337, 455),(240, 359)   |    0
+  (1, 1)                  |   99
   (759, 187),(662, 163)   |  162
   (948, 1201),(907, 1156) |  656
   (1444, 403),(1346, 344) |  846
  (5 rows)
  
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
*************** SELECT *, c <#> '(100, 100),(500, 500)':
*** 1637,1769 ****
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
-  (948, 1201),(907, 1156) | 1063
  (5 rows)
  
! -- kNN-based sorting
! SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
!              c             
! ---------------------------
!  (54, 38679),(3, 38602)
!  (83, 10271),(15, 10265)
!  (122, 46832),(64, 46762)
!  (167, 17214),(92, 17184)
!  (161, 24465),(107, 24374)
!  (162, 26040),(120, 25963)
!  (154, 4019),(138, 3990)
!  (259, 1850),(175, 1820)
!  (207, 40886),(179, 40879)
!  (288, 49588),(204, 49571)
!  (270, 32616),(226, 32607)
!  (318, 31489),(235, 31404)
!  (337, 455),(240, 359)
!  (270, 29508),(264, 29440)
!  (369, 1457),(278, 1409)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
!              c             
! ---------------------------
!  (30333, 50),(30273, 6)
!  (43301, 75),(43227, 43)
!  (19650, 142),(19630, 51)
!  (2424, 160),(2424, 81)
!  (3449, 171),(3354, 108)
!  (18037, 155),(17941, 109)
!  (28511, 208),(28479, 114)
!  (19946, 217),(19941, 118)
!  (16906, 191),(16816, 139)
!  (759, 187),(662, 163)
!  (22684, 266),(22656, 181)
!  (24423, 255),(24360, 213)
!  (45989, 249),(45910, 222)
!  (11399, 377),(11360, 294)
!  (12162, 389),(12103, 309)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
!                c               
! -------------------------------
!  (50027, 49230),(49951, 49214)
!  (49980, 35004),(49937, 34963)
!  (49985, 6436),(49927, 6338)
!  (49999, 27218),(49908, 27176)
!  (49954, 1340),(49905, 1294)
!  (49944, 25163),(49902, 25153)
!  (49981, 34876),(49898, 34786)
!  (49957, 43390),(49897, 43384)
!  (49853, 18504),(49848, 18503)
!  (49902, 41752),(49818, 41746)
!  (49907, 30225),(49810, 30158)
!  (49843, 5175),(49808, 5145)
!  (49887, 24274),(49805, 24184)
!  (49847, 7128),(49798, 7067)
!  (49820, 7990),(49771, 7967)
  (15 rows)
  
! SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
!                c               
! -------------------------------
!  (36311, 50073),(36258, 49987)
!  (30746, 50040),(30727, 49992)
!  (2168, 50012),(2108, 49914)
!  (21551, 49983),(21492, 49885)
!  (17954, 49975),(17865, 49915)
!  (3531, 49962),(3463, 49934)
!  (19128, 49932),(19112, 49849)
!  (31287, 49923),(31236, 49913)
!  (43925, 49912),(43888, 49878)
!  (29261, 49910),(29247, 49818)
!  (14913, 49873),(14849, 49836)
!  (20007, 49858),(19921, 49778)
!  (38266, 49852),(38233, 49844)
!  (37595, 49849),(37581, 49834)
!  (46151, 49848),(46058, 49830)
  (15 rows)
  
! -- same thing for index with points
! CREATE TABLE test_point(c cube);
! INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
! CREATE INDEX ON test_point USING gist(c);
! SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
!             c             
! --------------------------
!  (54, 38679, 3, 38602)
!  (83, 10271, 15, 10265)
!  (122, 46832, 64, 46762)
!  (154, 4019, 138, 3990)
!  (161, 24465, 107, 24374)
!  (162, 26040, 120, 25963)
!  (167, 17214, 92, 17184)
!  (207, 40886, 179, 40879)
!  (259, 1850, 175, 1820)
!  (270, 29508, 264, 29440)
!  (270, 32616, 226, 32607)
!  (288, 49588, 204, 49571)
!  (318, 31489, 235, 31404)
!  (326, 18837, 285, 18817)
!  (337, 455, 240, 359)
  (15 rows)
  
! SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
!               c               
! ------------------------------
!  (30746, 50040, 30727, 49992)
!  (36311, 50073, 36258, 49987)
!  (3531, 49962, 3463, 49934)
!  (17954, 49975, 17865, 49915)
!  (2168, 50012, 2108, 49914)
!  (31287, 49923, 31236, 49913)
!  (21551, 49983, 21492, 49885)
!  (43925, 49912, 43888, 49878)
!  (19128, 49932, 19112, 49849)
!  (38266, 49852, 38233, 49844)
!  (14913, 49873, 14849, 49836)
!  (37595, 49849, 37581, 49834)
!  (46151, 49848, 46058, 49830)
!  (29261, 49910, 29247, 49818)
!  (19233, 49824, 19185, 49794)
  (15 rows)
  
--- 1640,1842 ----
  -------------------------+------
   (337, 455),(240, 359)   |    0
   (759, 187),(662, 163)   |  162
+  (1, 1)                  |  198
   (1444, 403),(1346, 344) |  846
   (369, 1457),(278, 1409) |  909
  (5 rows)
  
! -- Test sorting by coordinates
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!         3 | (54, 38679),(3, 38602)
!        15 | (83, 10271),(15, 10265)
!        64 | (122, 46832),(64, 46762)
!        92 | (167, 17214),(92, 17184)
!       107 | (161, 24465),(107, 24374)
!       120 | (162, 26040),(120, 25963)
!       138 | (154, 4019),(138, 3990)
!       175 | (259, 1850),(175, 1820)
!       179 | (207, 40886),(179, 40879)
!       204 | (288, 49588),(204, 49571)
!       226 | (270, 32616),(226, 32607)
!       235 | (318, 31489),(235, 31404)
!       240 | (337, 455),(240, 359)
  (15 rows)
  
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!        54 | (54, 38679),(3, 38602)
!        83 | (83, 10271),(15, 10265)
!       122 | (122, 46832),(64, 46762)
!       154 | (154, 4019),(138, 3990)
!       161 | (161, 24465),(107, 24374)
!       162 | (162, 26040),(120, 25963)
!       167 | (167, 17214),(92, 17184)
!       207 | (207, 40886),(179, 40879)
!       259 | (259, 1850),(175, 1820)
!       270 | (270, 29508),(264, 29440)
!       270 | (270, 32616),(226, 32607)
!       288 | (288, 49588),(204, 49571)
!       318 | (318, 31489),(235, 31404)
  (15 rows)
  
! SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (100000)
!         1 | (1, 1)
!         6 | (30333, 50),(30273, 6)
!        43 | (43301, 75),(43227, 43)
!        51 | (19650, 142),(19630, 51)
!        81 | (2424, 160),(2424, 81)
!       108 | (3449, 171),(3354, 108)
!       109 | (18037, 155),(17941, 109)
!       114 | (28511, 208),(28479, 114)
!       118 | (19946, 217),(19941, 118)
!       139 | (16906, 191),(16816, 139)
!       163 | (759, 187),(662, 163)
!       181 | (22684, 266),(22656, 181)
!       213 | (24423, 255),(24360, 213)
!       222 | (45989, 249),(45910, 222)
  (15 rows)
  
! SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (100000)
!         1 | (1, 1)
!        50 | (30333, 50),(30273, 6)
!        75 | (43301, 75),(43227, 43)
!       142 | (19650, 142),(19630, 51)
!       155 | (18037, 155),(17941, 109)
!       160 | (2424, 160),(2424, 81)
!       171 | (3449, 171),(3354, 108)
!       187 | (759, 187),(662, 163)
!       191 | (16906, 191),(16816, 139)
!       208 | (28511, 208),(28479, 114)
!       217 | (19946, 217),(19941, 118)
!       249 | (45989, 249),(45910, 222)
!       255 | (24423, 255),(24360, 213)
!       266 | (22684, 266),(22656, 181)
  (15 rows)
  
! -- Same queries with sequential scan (should give the same results as above)
! RESET enable_seqscan;
! SET enable_indexscan = OFF;
! SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
!             c            |       dist       
! -------------------------+------------------
!  (337, 455),(240, 359)   |                0
!  (1, 1)                  | 140.007142674936
!  (759, 187),(662, 163)   |              162
!  (948, 1201),(907, 1156) | 772.000647668122
!  (1444, 403),(1346, 344) |              846
! (5 rows)
! 
! SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
!             c            | dist 
! -------------------------+------
!  (337, 455),(240, 359)   |    0
!  (1, 1)                  |   99
!  (759, 187),(662, 163)   |  162
!  (948, 1201),(907, 1156) |  656
!  (1444, 403),(1346, 344) |  846
! (5 rows)
! 
! SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
!             c            | dist 
! -------------------------+------
!  (337, 455),(240, 359)   |    0
!  (759, 187),(662, 163)   |  162
!  (1, 1)                  |  198
!  (1444, 403),(1346, 344) |  846
!  (369, 1457),(278, 1409) |  909
! (5 rows)
! 
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!         3 | (54, 38679),(3, 38602)
!        15 | (83, 10271),(15, 10265)
!        64 | (122, 46832),(64, 46762)
!        92 | (167, 17214),(92, 17184)
!       107 | (161, 24465),(107, 24374)
!       120 | (162, 26040),(120, 25963)
!       138 | (154, 4019),(138, 3990)
!       175 | (259, 1850),(175, 1820)
!       179 | (207, 40886),(179, 40879)
!       204 | (288, 49588),(204, 49571)
!       226 | (270, 32616),(226, 32607)
!       235 | (318, 31489),(235, 31404)
!       240 | (337, 455),(240, 359)
  (15 rows)
  
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
!  ?column? |             c             
! ----------+---------------------------
!         0 | (0, 100000)
!         1 | (1, 1)
!        54 | (54, 38679),(3, 38602)
!        83 | (83, 10271),(15, 10265)
!       122 | (122, 46832),(64, 46762)
!       154 | (154, 4019),(138, 3990)
!       161 | (161, 24465),(107, 24374)
!       162 | (162, 26040),(120, 25963)
!       167 | (167, 17214),(92, 17184)
!       207 | (207, 40886),(179, 40879)
!       259 | (259, 1850),(175, 1820)
!       270 | (270, 29508),(264, 29440)
!       270 | (270, 32616),(226, 32607)
!       288 | (288, 49588),(204, 49571)
!       318 | (318, 31489),(235, 31404)
  (15 rows)
  
+ SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (100000)
+         1 | (1, 1)
+         6 | (30333, 50),(30273, 6)
+        43 | (43301, 75),(43227, 43)
+        51 | (19650, 142),(19630, 51)
+        81 | (2424, 160),(2424, 81)
+       108 | (3449, 171),(3354, 108)
+       109 | (18037, 155),(17941, 109)
+       114 | (28511, 208),(28479, 114)
+       118 | (19946, 217),(19941, 118)
+       139 | (16906, 191),(16816, 139)
+       163 | (759, 187),(662, 163)
+       181 | (22684, 266),(22656, 181)
+       213 | (24423, 255),(24360, 213)
+       222 | (45989, 249),(45910, 222)
+ (15 rows)
+ 
+ SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+  ?column? |             c             
+ ----------+---------------------------
+         0 | (100000)
+         1 | (1, 1)
+        50 | (30333, 50),(30273, 6)
+        75 | (43301, 75),(43227, 43)
+       142 | (19650, 142),(19630, 51)
+       155 | (18037, 155),(17941, 109)
+       160 | (2424, 160),(2424, 81)
+       171 | (3449, 171),(3354, 108)
+       187 | (759, 187),(662, 163)
+       191 | (16906, 191),(16816, 139)
+       208 | (28511, 208),(28479, 114)
+       217 | (19946, 217),(19941, 118)
+       249 | (45989, 249),(45910, 222)
+       255 | (24423, 255),(24360, 213)
+       266 | (22684, 266),(22656, 181)
+ (15 rows)
+ 
+ RESET enable_indexscan;
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
new file mode 100644
index eb24576..efa1dbe
*** a/contrib/cube/sql/cube.sql
--- b/contrib/cube/sql/cube.sql
*************** SELECT c FROM test_cube WHERE c <@ '(300
*** 389,408 ****
  SELECT c FROM test_cube WHERE c <@ '(3000,1000),(0,0)' ORDER BY c;
  RESET enable_bitmapscan;
  
! -- kNN with index
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
  
! -- kNN-based sorting
! SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
! SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
! SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
! SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
  
! -- same thing for index with points
! CREATE TABLE test_point(c cube);
! INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
! CREATE INDEX ON test_point USING gist(c);
! SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
! SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
--- 389,417 ----
  SELECT c FROM test_cube WHERE c <@ '(3000,1000),(0,0)' ORDER BY c;
  RESET enable_bitmapscan;
  
! -- Test kNN
! INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
! SET enable_seqscan = false;
! 
! -- Test different metrics
  SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
  SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
  SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
  
! -- Test sorting by coordinates
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
! SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
! SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
  
! -- Same queries with sequential scan (should give the same results as above)
! RESET enable_seqscan;
! SET enable_indexscan = OFF;
! SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
! SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
! SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
! SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
! SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
! SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
! SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
! RESET enable_indexscan;
diff --git a/doc/src/sgml/cube.sgml b/doc/src/sgml/cube.sgml
new file mode 100644
index b995dc7..9cda8ca
*** a/doc/src/sgml/cube.sgml
--- b/doc/src/sgml/cube.sgml
***************
*** 186,195 ****
        <entry><literal>a ~&gt; n</literal></entry>
        <entry><type>float8</type></entry>
        <entry>
!         Get <replaceable>n</replaceable>-th coordinate in <quote>normalized</quote> cube
!         representation, in which the coordinates have been rearranged into
!         the form <quote>lower left &mdash; upper right</quote>; that is, the
!         smaller endpoint along each dimension appears first.
        </entry>
       </row>
  
--- 186,196 ----
        <entry><literal>a ~&gt; n</literal></entry>
        <entry><type>float8</type></entry>
        <entry>
!         Get <replaceable>n</replaceable>-th coordinate of cube in following way:
!         n = 2 * k - 1 means lower bound of <replaceable>k</replaceable>-th
!         dimension, n = 2 * k means upper bound of
!         <replaceable>k</replaceable>-th dimension.  This operator is designed
!         for KNN-GiST support.
        </entry>
       </row>
  
0002-cube-knn-negative-coordinate-2.patchapplication/octet-stream; name=0002-cube-knn-negative-coordinate-2.patchDownload
diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
new file mode 100644
index dcc0850..d96ca1e
*** a/contrib/cube/cube.c
--- b/contrib/cube/cube.c
*************** g_cube_distance(PG_FUNCTION_ARGS)
*** 1343,1354 ****
  		 */
  		int			coord = PG_GETARG_INT32(1);
  		bool		isLeaf = GistPageIsLeaf(entry->page);
  
  		/* 0 is the only unsupported coordinate value */
! 		if (coord <= 0)
  			ereport(ERROR,
  					(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
! 					 errmsg("cube index %d is out of bounds", coord)));
  
  		if (coord <= 2 * DIM(cube))
  		{
--- 1343,1362 ----
  		 */
  		int			coord = PG_GETARG_INT32(1);
  		bool		isLeaf = GistPageIsLeaf(entry->page);
+ 		bool		inverse = false;
  
  		/* 0 is the only unsupported coordinate value */
! 		if (coord == 0)
  			ereport(ERROR,
  					(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
! 					 errmsg("zero cube index is not defined")));
! 
! 		/* Return inversed value for negative coordinate */
! 		if (coord < 0)
! 		{
! 			coord = -coord;
! 			inverse = true;
! 		}
  
  		if (coord <= 2 * DIM(cube))
  		{
*************** g_cube_distance(PG_FUNCTION_ARGS)
*** 1376,1384 ****
  					/*
  					 * For non-leaf we should always return lower bound,
  					 * because even upper bound of a child in the subtree can
! 					 * be as small as our lower bound.
  					 */
! 					retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
  				}
  			}
  		}
--- 1384,1397 ----
  					/*
  					 * For non-leaf we should always return lower bound,
  					 * because even upper bound of a child in the subtree can
! 					 * be as small as our lower bound.  For inversed case we
! 					 * return upper bound because it becomes lower bound for
! 					 * inversed value.
  					 */
! 					if (!inverse)
! 						retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
! 					else
! 						retval = Max(cube->x[index], cube->x[index + DIM(cube)]);
  				}
  			}
  		}
*************** g_cube_distance(PG_FUNCTION_ARGS)
*** 1386,1391 ****
--- 1399,1408 ----
  		{
  			retval = 0.0;
  		}
+ 
+ 		/* Inverse return value if needed */
+ 		if (inverse)
+ 			retval = -retval;
  	}
  	else
  	{
*************** cube_coord(PG_FUNCTION_ARGS)
*** 1542,1552 ****
   * getter.  Moreover, indexed dataset may contain cubes of different dimensions
   * number.  Accordingly, this coordinate getter should be able to return
   * lower/upper bound for particular dimension independently on number of cube
!  * dimensions.
   *
   * Long story short, this function uses following meaning of coordinates:
   * # (2 * N - 1) -- lower bound of Nth dimension,
!  * # (2 * N) -- upper bound of Nth dimension.
   *
   * When given coordinate exceeds number of cube dimensions, then 0 returned
   * (reproducing logic of GiST indexing of variable-length cubes).
--- 1559,1573 ----
   * getter.  Moreover, indexed dataset may contain cubes of different dimensions
   * number.  Accordingly, this coordinate getter should be able to return
   * lower/upper bound for particular dimension independently on number of cube
!  * dimensions.  Also, KNN-GiST supports only ascending sorting.  In order to
!  * support descending sorting, this function returns inverse of value when
!  * negative coordinate is given.
   *
   * Long story short, this function uses following meaning of coordinates:
   * # (2 * N - 1) -- lower bound of Nth dimension,
!  * # (2 * N) -- upper bound of Nth dimension,
!  * # - (2 * N - 1) -- negative of lower bound of Nth dimension,
!  * # - (2 * N) -- negative of upper bound of Nth dimension.
   *
   * When given coordinate exceeds number of cube dimensions, then 0 returned
   * (reproducing logic of GiST indexing of variable-length cubes).
*************** cube_coord_llur(PG_FUNCTION_ARGS)
*** 1560,1569 ****
  	float8		result;
  
  	/* 0 is the only unsupported coordinate value */
! 	if (coord <= 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
! 				 errmsg("cube index %d is out of bounds", coord)));
  
  	if (coord <= 2 * DIM(cube))
  	{
--- 1581,1597 ----
  	float8		result;
  
  	/* 0 is the only unsupported coordinate value */
! 	if (coord == 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
! 				 errmsg("zero cube index is not defined")));
! 
! 	/* Return inversed value for negative coordinate */
! 	if (coord < 0)
! 	{
! 		coord = -coord;
! 		inverse = true;
! 	}
  
  	if (coord <= 2 * DIM(cube))
  	{
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
new file mode 100644
index c586a73..6378db3
*** a/contrib/cube/expected/cube.out
--- b/contrib/cube/expected/cube.out
*************** SELECT cube(array[40,50,60], array[10,20
*** 1554,1560 ****
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>0;
! ERROR:  cube index 0 is out of bounds
  SELECT cube(array[40,50,60], array[10,20,30])~>4;
   ?column? 
  ----------
--- 1554,1560 ----
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>0;
! ERROR:  zero cube index is not defined
  SELECT cube(array[40,50,60], array[10,20,30])~>4;
   ?column? 
  ----------
*************** SELECT cube(array[40,50,60], array[10,20
*** 1562,1568 ****
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
! ERROR:  cube index -1 is out of bounds
  -- Load some example data and build the index
  --
  CREATE TABLE test_cube (c cube);
--- 1562,1572 ----
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
!  ?column? 
! ----------
!       -10
! (1 row)
! 
  -- Load some example data and build the index
  --
  CREATE TABLE test_cube (c cube);
*************** SELECT c~>4, c FROM test_cube ORDER BY c
*** 1726,1731 ****
--- 1730,1815 ----
        266 | (22684, 266),(22656, 181)
  (15 rows)
  
+ SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -49951 | (50027, 49230),(49951, 49214)
+    -49937 | (49980, 35004),(49937, 34963)
+    -49927 | (49985, 6436),(49927, 6338)
+    -49908 | (49999, 27218),(49908, 27176)
+    -49905 | (49954, 1340),(49905, 1294)
+    -49902 | (49944, 25163),(49902, 25153)
+    -49898 | (49981, 34876),(49898, 34786)
+    -49897 | (49957, 43390),(49897, 43384)
+    -49848 | (49853, 18504),(49848, 18503)
+    -49818 | (49902, 41752),(49818, 41746)
+    -49810 | (49907, 30225),(49810, 30158)
+    -49808 | (49843, 5175),(49808, 5145)
+    -49805 | (49887, 24274),(49805, 24184)
+    -49798 | (49847, 7128),(49798, 7067)
+ (15 rows)
+ 
+ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -50027 | (50027, 49230),(49951, 49214)
+    -49999 | (49999, 27218),(49908, 27176)
+    -49985 | (49985, 6436),(49927, 6338)
+    -49981 | (49981, 34876),(49898, 34786)
+    -49980 | (49980, 35004),(49937, 34963)
+    -49957 | (49957, 43390),(49897, 43384)
+    -49954 | (49954, 1340),(49905, 1294)
+    -49944 | (49944, 25163),(49902, 25153)
+    -49907 | (49907, 30225),(49810, 30158)
+    -49902 | (49902, 41752),(49818, 41746)
+    -49887 | (49887, 24274),(49805, 24184)
+    -49853 | (49853, 18504),(49848, 18503)
+    -49847 | (49847, 7128),(49798, 7067)
+    -49843 | (49843, 5175),(49808, 5145)
+ (15 rows)
+ 
+ SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -49992 | (30746, 50040),(30727, 49992)
+    -49987 | (36311, 50073),(36258, 49987)
+    -49934 | (3531, 49962),(3463, 49934)
+    -49915 | (17954, 49975),(17865, 49915)
+    -49914 | (2168, 50012),(2108, 49914)
+    -49913 | (31287, 49923),(31236, 49913)
+    -49885 | (21551, 49983),(21492, 49885)
+    -49878 | (43925, 49912),(43888, 49878)
+    -49849 | (19128, 49932),(19112, 49849)
+    -49844 | (38266, 49852),(38233, 49844)
+    -49836 | (14913, 49873),(14849, 49836)
+    -49834 | (37595, 49849),(37581, 49834)
+    -49830 | (46151, 49848),(46058, 49830)
+    -49818 | (29261, 49910),(29247, 49818)
+ (15 rows)
+ 
+ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -50073 | (36311, 50073),(36258, 49987)
+    -50040 | (30746, 50040),(30727, 49992)
+    -50012 | (2168, 50012),(2108, 49914)
+    -49983 | (21551, 49983),(21492, 49885)
+    -49975 | (17954, 49975),(17865, 49915)
+    -49962 | (3531, 49962),(3463, 49934)
+    -49932 | (19128, 49932),(19112, 49849)
+    -49923 | (31287, 49923),(31236, 49913)
+    -49912 | (43925, 49912),(43888, 49878)
+    -49910 | (29261, 49910),(29247, 49818)
+    -49873 | (14913, 49873),(14849, 49836)
+    -49858 | (20007, 49858),(19921, 49778)
+    -49852 | (38266, 49852),(38233, 49844)
+    -49849 | (37595, 49849),(37581, 49834)
+ (15 rows)
+ 
  -- Same queries with sequential scan (should give the same results as above)
  RESET enable_seqscan;
  SET enable_indexscan = OFF;
*************** SELECT c~>4, c FROM test_cube ORDER BY c
*** 1839,1842 ****
--- 1923,2006 ----
        266 | (22684, 266),(22656, 181)
  (15 rows)
  
+ SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -49951 | (50027, 49230),(49951, 49214)
+    -49937 | (49980, 35004),(49937, 34963)
+    -49927 | (49985, 6436),(49927, 6338)
+    -49908 | (49999, 27218),(49908, 27176)
+    -49905 | (49954, 1340),(49905, 1294)
+    -49902 | (49944, 25163),(49902, 25153)
+    -49898 | (49981, 34876),(49898, 34786)
+    -49897 | (49957, 43390),(49897, 43384)
+    -49848 | (49853, 18504),(49848, 18503)
+    -49818 | (49902, 41752),(49818, 41746)
+    -49810 | (49907, 30225),(49810, 30158)
+    -49808 | (49843, 5175),(49808, 5145)
+    -49805 | (49887, 24274),(49805, 24184)
+    -49798 | (49847, 7128),(49798, 7067)
+ (15 rows)
+ 
+ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -50027 | (50027, 49230),(49951, 49214)
+    -49999 | (49999, 27218),(49908, 27176)
+    -49985 | (49985, 6436),(49927, 6338)
+    -49981 | (49981, 34876),(49898, 34786)
+    -49980 | (49980, 35004),(49937, 34963)
+    -49957 | (49957, 43390),(49897, 43384)
+    -49954 | (49954, 1340),(49905, 1294)
+    -49944 | (49944, 25163),(49902, 25153)
+    -49907 | (49907, 30225),(49810, 30158)
+    -49902 | (49902, 41752),(49818, 41746)
+    -49887 | (49887, 24274),(49805, 24184)
+    -49853 | (49853, 18504),(49848, 18503)
+    -49847 | (49847, 7128),(49798, 7067)
+    -49843 | (49843, 5175),(49808, 5145)
+ (15 rows)
+ 
+ SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -49992 | (30746, 50040),(30727, 49992)
+    -49987 | (36311, 50073),(36258, 49987)
+    -49934 | (3531, 49962),(3463, 49934)
+    -49915 | (17954, 49975),(17865, 49915)
+    -49914 | (2168, 50012),(2108, 49914)
+    -49913 | (31287, 49923),(31236, 49913)
+    -49885 | (21551, 49983),(21492, 49885)
+    -49878 | (43925, 49912),(43888, 49878)
+    -49849 | (19128, 49932),(19112, 49849)
+    -49844 | (38266, 49852),(38233, 49844)
+    -49836 | (14913, 49873),(14849, 49836)
+    -49834 | (37595, 49849),(37581, 49834)
+    -49830 | (46151, 49848),(46058, 49830)
+    -49818 | (29261, 49910),(29247, 49818)
+ (15 rows)
+ 
+ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -50073 | (36311, 50073),(36258, 49987)
+    -50040 | (30746, 50040),(30727, 49992)
+    -50012 | (2168, 50012),(2108, 49914)
+    -49983 | (21551, 49983),(21492, 49885)
+    -49975 | (17954, 49975),(17865, 49915)
+    -49962 | (3531, 49962),(3463, 49934)
+    -49932 | (19128, 49932),(19112, 49849)
+    -49923 | (31287, 49923),(31236, 49913)
+    -49912 | (43925, 49912),(43888, 49878)
+    -49910 | (29261, 49910),(29247, 49818)
+    -49873 | (14913, 49873),(14849, 49836)
+    -49858 | (20007, 49858),(19921, 49778)
+    -49852 | (38266, 49852),(38233, 49844)
+    -49849 | (37595, 49849),(37581, 49834)
+ (15 rows)
+ 
  RESET enable_indexscan;
diff --git a/contrib/cube/expected/cube_2.out b/contrib/cube/expected/cube_2.out
new file mode 100644
index 8c75e27..75fe405
*** a/contrib/cube/expected/cube_2.out
--- b/contrib/cube/expected/cube_2.out
*************** SELECT cube(array[40,50,60], array[10,20
*** 1554,1560 ****
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>0;
! ERROR:  cube index 0 is out of bounds
  SELECT cube(array[40,50,60], array[10,20,30])~>4;
   ?column? 
  ----------
--- 1554,1560 ----
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>0;
! ERROR:  zero cube index is not defined
  SELECT cube(array[40,50,60], array[10,20,30])~>4;
   ?column? 
  ----------
*************** SELECT cube(array[40,50,60], array[10,20
*** 1562,1568 ****
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
! ERROR:  cube index -1 is out of bounds
  -- Load some example data and build the index
  --
  CREATE TABLE test_cube (c cube);
--- 1562,1572 ----
  (1 row)
  
  SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
!  ?column? 
! ----------
!       -10
! (1 row)
! 
  -- Load some example data and build the index
  --
  CREATE TABLE test_cube (c cube);
*************** SELECT c~>4, c FROM test_cube ORDER BY c
*** 1726,1731 ****
--- 1730,1815 ----
        266 | (22684, 266),(22656, 181)
  (15 rows)
  
+ SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -49951 | (50027, 49230),(49951, 49214)
+    -49937 | (49980, 35004),(49937, 34963)
+    -49927 | (49985, 6436),(49927, 6338)
+    -49908 | (49999, 27218),(49908, 27176)
+    -49905 | (49954, 1340),(49905, 1294)
+    -49902 | (49944, 25163),(49902, 25153)
+    -49898 | (49981, 34876),(49898, 34786)
+    -49897 | (49957, 43390),(49897, 43384)
+    -49848 | (49853, 18504),(49848, 18503)
+    -49818 | (49902, 41752),(49818, 41746)
+    -49810 | (49907, 30225),(49810, 30158)
+    -49808 | (49843, 5175),(49808, 5145)
+    -49805 | (49887, 24274),(49805, 24184)
+    -49798 | (49847, 7128),(49798, 7067)
+ (15 rows)
+ 
+ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -50027 | (50027, 49230),(49951, 49214)
+    -49999 | (49999, 27218),(49908, 27176)
+    -49985 | (49985, 6436),(49927, 6338)
+    -49981 | (49981, 34876),(49898, 34786)
+    -49980 | (49980, 35004),(49937, 34963)
+    -49957 | (49957, 43390),(49897, 43384)
+    -49954 | (49954, 1340),(49905, 1294)
+    -49944 | (49944, 25163),(49902, 25153)
+    -49907 | (49907, 30225),(49810, 30158)
+    -49902 | (49902, 41752),(49818, 41746)
+    -49887 | (49887, 24274),(49805, 24184)
+    -49853 | (49853, 18504),(49848, 18503)
+    -49847 | (49847, 7128),(49798, 7067)
+    -49843 | (49843, 5175),(49808, 5145)
+ (15 rows)
+ 
+ SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -49992 | (30746, 50040),(30727, 49992)
+    -49987 | (36311, 50073),(36258, 49987)
+    -49934 | (3531, 49962),(3463, 49934)
+    -49915 | (17954, 49975),(17865, 49915)
+    -49914 | (2168, 50012),(2108, 49914)
+    -49913 | (31287, 49923),(31236, 49913)
+    -49885 | (21551, 49983),(21492, 49885)
+    -49878 | (43925, 49912),(43888, 49878)
+    -49849 | (19128, 49932),(19112, 49849)
+    -49844 | (38266, 49852),(38233, 49844)
+    -49836 | (14913, 49873),(14849, 49836)
+    -49834 | (37595, 49849),(37581, 49834)
+    -49830 | (46151, 49848),(46058, 49830)
+    -49818 | (29261, 49910),(29247, 49818)
+ (15 rows)
+ 
+ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -50073 | (36311, 50073),(36258, 49987)
+    -50040 | (30746, 50040),(30727, 49992)
+    -50012 | (2168, 50012),(2108, 49914)
+    -49983 | (21551, 49983),(21492, 49885)
+    -49975 | (17954, 49975),(17865, 49915)
+    -49962 | (3531, 49962),(3463, 49934)
+    -49932 | (19128, 49932),(19112, 49849)
+    -49923 | (31287, 49923),(31236, 49913)
+    -49912 | (43925, 49912),(43888, 49878)
+    -49910 | (29261, 49910),(29247, 49818)
+    -49873 | (14913, 49873),(14849, 49836)
+    -49858 | (20007, 49858),(19921, 49778)
+    -49852 | (38266, 49852),(38233, 49844)
+    -49849 | (37595, 49849),(37581, 49834)
+ (15 rows)
+ 
  -- Same queries with sequential scan (should give the same results as above)
  RESET enable_seqscan;
  SET enable_indexscan = OFF;
*************** SELECT c~>4, c FROM test_cube ORDER BY c
*** 1839,1842 ****
--- 1923,2006 ----
        266 | (22684, 266),(22656, 181)
  (15 rows)
  
+ SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -49951 | (50027, 49230),(49951, 49214)
+    -49937 | (49980, 35004),(49937, 34963)
+    -49927 | (49985, 6436),(49927, 6338)
+    -49908 | (49999, 27218),(49908, 27176)
+    -49905 | (49954, 1340),(49905, 1294)
+    -49902 | (49944, 25163),(49902, 25153)
+    -49898 | (49981, 34876),(49898, 34786)
+    -49897 | (49957, 43390),(49897, 43384)
+    -49848 | (49853, 18504),(49848, 18503)
+    -49818 | (49902, 41752),(49818, 41746)
+    -49810 | (49907, 30225),(49810, 30158)
+    -49808 | (49843, 5175),(49808, 5145)
+    -49805 | (49887, 24274),(49805, 24184)
+    -49798 | (49847, 7128),(49798, 7067)
+ (15 rows)
+ 
+ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (100000)
+    -50027 | (50027, 49230),(49951, 49214)
+    -49999 | (49999, 27218),(49908, 27176)
+    -49985 | (49985, 6436),(49927, 6338)
+    -49981 | (49981, 34876),(49898, 34786)
+    -49980 | (49980, 35004),(49937, 34963)
+    -49957 | (49957, 43390),(49897, 43384)
+    -49954 | (49954, 1340),(49905, 1294)
+    -49944 | (49944, 25163),(49902, 25153)
+    -49907 | (49907, 30225),(49810, 30158)
+    -49902 | (49902, 41752),(49818, 41746)
+    -49887 | (49887, 24274),(49805, 24184)
+    -49853 | (49853, 18504),(49848, 18503)
+    -49847 | (49847, 7128),(49798, 7067)
+    -49843 | (49843, 5175),(49808, 5145)
+ (15 rows)
+ 
+ SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -49992 | (30746, 50040),(30727, 49992)
+    -49987 | (36311, 50073),(36258, 49987)
+    -49934 | (3531, 49962),(3463, 49934)
+    -49915 | (17954, 49975),(17865, 49915)
+    -49914 | (2168, 50012),(2108, 49914)
+    -49913 | (31287, 49923),(31236, 49913)
+    -49885 | (21551, 49983),(21492, 49885)
+    -49878 | (43925, 49912),(43888, 49878)
+    -49849 | (19128, 49932),(19112, 49849)
+    -49844 | (38266, 49852),(38233, 49844)
+    -49836 | (14913, 49873),(14849, 49836)
+    -49834 | (37595, 49849),(37581, 49834)
+    -49830 | (46151, 49848),(46058, 49830)
+    -49818 | (29261, 49910),(29247, 49818)
+ (15 rows)
+ 
+ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
+  ?column? |               c               
+ ----------+-------------------------------
+   -100000 | (0, 100000)
+    -50073 | (36311, 50073),(36258, 49987)
+    -50040 | (30746, 50040),(30727, 49992)
+    -50012 | (2168, 50012),(2108, 49914)
+    -49983 | (21551, 49983),(21492, 49885)
+    -49975 | (17954, 49975),(17865, 49915)
+    -49962 | (3531, 49962),(3463, 49934)
+    -49932 | (19128, 49932),(19112, 49849)
+    -49923 | (31287, 49923),(31236, 49913)
+    -49912 | (43925, 49912),(43888, 49878)
+    -49910 | (29261, 49910),(29247, 49818)
+    -49873 | (14913, 49873),(14849, 49836)
+    -49858 | (20007, 49858),(19921, 49778)
+    -49852 | (38266, 49852),(38233, 49844)
+    -49849 | (37595, 49849),(37581, 49834)
+ (15 rows)
+ 
  RESET enable_indexscan;
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
new file mode 100644
index efa1dbe..f599e7f
*** a/contrib/cube/sql/cube.sql
--- b/contrib/cube/sql/cube.sql
*************** SELECT c~>1, c FROM test_cube ORDER BY c
*** 403,408 ****
--- 403,412 ----
  SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
  SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
  SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+ SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
  
  -- Same queries with sequential scan (should give the same results as above)
  RESET enable_seqscan;
*************** SELECT c~>1, c FROM test_cube ORDER BY c
*** 414,417 ****
--- 418,425 ----
  SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
  SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
  SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+ SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
  RESET enable_indexscan;
diff --git a/doc/src/sgml/cube.sgml b/doc/src/sgml/cube.sgml
new file mode 100644
index 9cda8ca..e010305
*** a/doc/src/sgml/cube.sgml
--- b/doc/src/sgml/cube.sgml
***************
*** 189,196 ****
          Get <replaceable>n</replaceable>-th coordinate of cube in following way:
          n = 2 * k - 1 means lower bound of <replaceable>k</replaceable>-th
          dimension, n = 2 * k means upper bound of
!         <replaceable>k</replaceable>-th dimension.  This operator is designed
!         for KNN-GiST support.
        </entry>
       </row>
  
--- 189,197 ----
          Get <replaceable>n</replaceable>-th coordinate of cube in following way:
          n = 2 * k - 1 means lower bound of <replaceable>k</replaceable>-th
          dimension, n = 2 * k means upper bound of
!         <replaceable>k</replaceable>-th dimension.  Negative
!         <replaceable>n</replaceable> denotes inversed value of corresponding
!         positive coordinate.  This operator is designed for KNN-GiST support.
        </entry>
       </row>
  
#13Michael Paquier
michael.paquier@gmail.com
In reply to: Alexander Korotkov (#10)
Re: [HACKERS] CUBE seems a bit confused about ORDER BY

On Wed, Nov 29, 2017 at 7:59 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:

Sure, patch got some review. I've no objection against moving this to the
next commitfest though.

Please note that as this is qualified as a bug fix, I was not going to
mark it as returned with feedback or such. We want to keep track of
fixes that are bugs and potentially need back-patching.

Since, these patches include bug fix, it's possible that someone will commit
it before next commitfest.

Sure. Committers are free to commit patches they see suited for
integration even out of the CF time.
--
Michael

#14Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alexander Korotkov (#12)
2 attachment(s)
Re: [HACKERS] CUBE seems a bit confused about ORDER BY

On Wed, Nov 29, 2017 at 3:10 PM, Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:

I'll try to provide meaningful review next week.

Cool, thanks.

Andrey Borodin complained through Telegram that he can't apply my patches
using "git apply". After investigation of this problem, it appears that
the reason is that I still use git context diff setup according to our wiki
instruction (https://wiki.postgresql.org/wiki/Working_with_Git). I've look
around some threads and realized that I'm probably the only guy who still
sends context diff patches.

Attached patchset in universal diff format.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-cube-knn-fix-3.patchapplication/octet-stream; name=0001-cube-knn-fix-3.patchDownload
diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
index 3e3d83323c..dcc0850aa9 100644
--- a/contrib/cube/cube.c
+++ b/contrib/cube/cube.c
@@ -1337,15 +1337,55 @@ g_cube_distance(PG_FUNCTION_ARGS)
 
 	if (strategy == CubeKNNDistanceCoord)
 	{
+		/*
+		 * Handle ordering by ~> operator.  See comments of cube_coord_llur()
+		 * for details
+		 */
 		int			coord = PG_GETARG_INT32(1);
+		bool		isLeaf = GistPageIsLeaf(entry->page);
 
-		if (DIM(cube) == 0)
-			retval = 0.0;
-		else if (IS_POINT(cube))
-			retval = cube->x[(coord - 1) % DIM(cube)];
+		/* 0 is the only unsupported coordinate value */
+		if (coord <= 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
+					 errmsg("cube index %d is out of bounds", coord)));
+
+		if (coord <= 2 * DIM(cube))
+		{
+			/* dimension index */
+			int		index = (coord - 1) / 2;
+			/* whether this is upper bound (lower bound otherwise) */
+			bool	upper = ((coord - 1) % 2 == 1);
+
+			if (IS_POINT(cube))
+			{
+				retval = cube->x[index];
+			}
+			else
+			{
+				if (isLeaf)
+				{
+					/* For leaf just return required upper/lower bound */
+					if (upper)
+						retval = Max(cube->x[index], cube->x[index + DIM(cube)]);
+					else
+						retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
+				}
+				else
+				{
+					/*
+					 * For non-leaf we should always return lower bound,
+					 * because even upper bound of a child in the subtree can
+					 * be as small as our lower bound.
+					 */
+					retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
+				}
+			}
+		}
 		else
-			retval = Min(cube->x[(coord - 1) % DIM(cube)],
-						 cube->x[(coord - 1) % DIM(cube) + DIM(cube)]);
+		{
+			retval = 0.0;
+		}
 	}
 	else
 	{
@@ -1492,43 +1532,73 @@ cube_coord(PG_FUNCTION_ARGS)
 }
 
 
-/*
- * This function works like cube_coord(),
- * but rearranges coordinates of corners to get cube representation
- * in the form of (lower left, upper right).
- * For historical reasons that extension allows us to create cubes in form
- * ((2,1),(1,2)) and instead of normalizing such cube to ((1,1),(2,2)) it
- * stores cube in original way. But to get cubes ordered by one of dimensions
- * directly from the index without extra sort step we need some
- * representation-independent coordinate getter. This function implements it.
+/*----
+ * This function works like cube_coord(), but rearranges coordinates in the
+ * way suitable to support coordinate ordering using KNN-GiST.  For historical
+ * reasons this extension allows us to create cubes in form ((2,1),(1,2)) and
+ * instead of normalizing such cube to ((1,1),(2,2)) it stores cube in original
+ * way.  But in order to get cubes ordered by one of dimensions from the index
+ * without explicit sort step we need this representation-independent coordinate
+ * getter.  Moreover, indexed dataset may contain cubes of different dimensions
+ * number.  Accordingly, this coordinate getter should be able to return
+ * lower/upper bound for particular dimension independently on number of cube
+ * dimensions.
+ *
+ * Long story short, this function uses following meaning of coordinates:
+ * # (2 * N - 1) -- lower bound of Nth dimension,
+ * # (2 * N) -- upper bound of Nth dimension.
+ *
+ * When given coordinate exceeds number of cube dimensions, then 0 returned
+ * (reproducing logic of GiST indexing of variable-length cubes).
  */
 Datum
 cube_coord_llur(PG_FUNCTION_ARGS)
 {
 	NDBOX	   *cube = PG_GETARG_NDBOX_P(0);
 	int			coord = PG_GETARG_INT32(1);
+	bool		inverse = false;
+	float8		result;
 
-	if (coord <= 0 || coord > 2 * DIM(cube))
+	/* 0 is the only unsupported coordinate value */
+	if (coord <= 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
 				 errmsg("cube index %d is out of bounds", coord)));
 
-	if (coord <= DIM(cube))
+	if (coord <= 2 * DIM(cube))
 	{
+		/* dimension index */
+		int		index = (coord - 1) / 2;
+		/* whether this is upper bound (lower bound otherwise) */
+		bool	upper = ((coord - 1) % 2 == 1);
+
 		if (IS_POINT(cube))
-			PG_RETURN_FLOAT8(cube->x[coord - 1]);
+		{
+			result = cube->x[index];
+		}
 		else
-			PG_RETURN_FLOAT8(Min(cube->x[coord - 1],
-								 cube->x[coord - 1 + DIM(cube)]));
+		{
+			if (upper)
+				result = Max(cube->x[index], cube->x[index + DIM(cube)]);
+			else
+				result = Min(cube->x[index], cube->x[index + DIM(cube)]);
+		}
 	}
 	else
 	{
-		if (IS_POINT(cube))
-			PG_RETURN_FLOAT8(cube->x[(coord - 1) % DIM(cube)]);
-		else
-			PG_RETURN_FLOAT8(Max(cube->x[coord - 1],
-								 cube->x[coord - 1 - DIM(cube)]));
+		/*
+		 * Return zero if coordinate is out of bound.  That reproduces logic of
+		 * how cubes with low dimension number are expanded during GiST
+		 * indexing.
+		 */
+		result = 0.0;
 	}
+
+	/* Inverse value if needed */
+	if (inverse)
+		result = -result;
+
+	PG_RETURN_FLOAT8(result);
 }
 
 /* Increase or decrease box size by a radius in at least n dimensions. */
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
index c430b4e1f0..c586a73727 100644
--- a/contrib/cube/expected/cube.out
+++ b/contrib/cube/expected/cube.out
@@ -1532,25 +1532,25 @@ SELECT cube(array[40,50,60], array[10,20,30])~>1;
 SELECT cube(array[10,20,30], array[40,50,60])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[10,20,30], array[40,50,60])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>0;
@@ -1558,7 +1558,7 @@ ERROR:  cube index 0 is out of bounds
 SELECT cube(array[40,50,60], array[10,20,30])~>4;
  ?column? 
 ----------
-       40
+       50
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
@@ -1611,25 +1611,28 @@ SELECT c FROM test_cube WHERE c <@ '(3000,1000),(0,0)' ORDER BY c;
 (4 rows)
 
 RESET enable_bitmapscan;
--- kNN with index
+-- Test kNN
+INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
+SET enable_seqscan = false;
+-- Test different metrics
 SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            |       dist       
 -------------------------+------------------
  (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
  (759, 187),(662, 163)   |              162
  (948, 1201),(907, 1156) | 772.000647668122
  (1444, 403),(1346, 344) |              846
- (369, 1457),(278, 1409) |              909
 (5 rows)
 
 SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            | dist 
 -------------------------+------
  (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
  (759, 187),(662, 163)   |  162
  (948, 1201),(907, 1156) |  656
  (1444, 403),(1346, 344) |  846
- (369, 1457),(278, 1409) |  909
 (5 rows)
 
 SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
@@ -1637,133 +1640,203 @@ SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c
 -------------------------+------
  (337, 455),(240, 359)   |    0
  (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
  (1444, 403),(1346, 344) |  846
  (369, 1457),(278, 1409) |  909
- (948, 1201),(907, 1156) | 1063
 (5 rows)
 
--- kNN-based sorting
-SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
-             c             
----------------------------
- (54, 38679),(3, 38602)
- (83, 10271),(15, 10265)
- (122, 46832),(64, 46762)
- (167, 17214),(92, 17184)
- (161, 24465),(107, 24374)
- (162, 26040),(120, 25963)
- (154, 4019),(138, 3990)
- (259, 1850),(175, 1820)
- (207, 40886),(179, 40879)
- (288, 49588),(204, 49571)
- (270, 32616),(226, 32607)
- (318, 31489),(235, 31404)
- (337, 455),(240, 359)
- (270, 29508),(264, 29440)
- (369, 1457),(278, 1409)
+-- Test sorting by coordinates
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
-             c             
----------------------------
- (30333, 50),(30273, 6)
- (43301, 75),(43227, 43)
- (19650, 142),(19630, 51)
- (2424, 160),(2424, 81)
- (3449, 171),(3354, 108)
- (18037, 155),(17941, 109)
- (28511, 208),(28479, 114)
- (19946, 217),(19941, 118)
- (16906, 191),(16816, 139)
- (759, 187),(662, 163)
- (22684, 266),(22656, 181)
- (24423, 255),(24360, 213)
- (45989, 249),(45910, 222)
- (11399, 377),(11360, 294)
- (12162, 389),(12103, 309)
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
-               c               
--------------------------------
- (50027, 49230),(49951, 49214)
- (49980, 35004),(49937, 34963)
- (49985, 6436),(49927, 6338)
- (49999, 27218),(49908, 27176)
- (49954, 1340),(49905, 1294)
- (49944, 25163),(49902, 25153)
- (49981, 34876),(49898, 34786)
- (49957, 43390),(49897, 43384)
- (49853, 18504),(49848, 18503)
- (49902, 41752),(49818, 41746)
- (49907, 30225),(49810, 30158)
- (49843, 5175),(49808, 5145)
- (49887, 24274),(49805, 24184)
- (49847, 7128),(49798, 7067)
- (49820, 7990),(49771, 7967)
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
-               c               
--------------------------------
- (36311, 50073),(36258, 49987)
- (30746, 50040),(30727, 49992)
- (2168, 50012),(2108, 49914)
- (21551, 49983),(21492, 49885)
- (17954, 49975),(17865, 49915)
- (3531, 49962),(3463, 49934)
- (19128, 49932),(19112, 49849)
- (31287, 49923),(31236, 49913)
- (43925, 49912),(43888, 49878)
- (29261, 49910),(29247, 49818)
- (14913, 49873),(14849, 49836)
- (20007, 49858),(19921, 49778)
- (38266, 49852),(38233, 49844)
- (37595, 49849),(37581, 49834)
- (46151, 49848),(46058, 49830)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
--- same thing for index with points
-CREATE TABLE test_point(c cube);
-INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
-CREATE INDEX ON test_point USING gist(c);
-SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
-            c             
---------------------------
- (54, 38679, 3, 38602)
- (83, 10271, 15, 10265)
- (122, 46832, 64, 46762)
- (154, 4019, 138, 3990)
- (161, 24465, 107, 24374)
- (162, 26040, 120, 25963)
- (167, 17214, 92, 17184)
- (207, 40886, 179, 40879)
- (259, 1850, 175, 1820)
- (270, 29508, 264, 29440)
- (270, 32616, 226, 32607)
- (288, 49588, 204, 49571)
- (318, 31489, 235, 31404)
- (326, 18837, 285, 18817)
- (337, 455, 240, 359)
+-- Same queries with sequential scan (should give the same results as above)
+RESET enable_seqscan;
+SET enable_indexscan = OFF;
+SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            |       dist       
+-------------------------+------------------
+ (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
+ (759, 187),(662, 163)   |              162
+ (948, 1201),(907, 1156) | 772.000647668122
+ (1444, 403),(1346, 344) |              846
+(5 rows)
+
+SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
+ (759, 187),(662, 163)   |  162
+ (948, 1201),(907, 1156) |  656
+ (1444, 403),(1346, 344) |  846
+(5 rows)
+
+SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
+ (1444, 403),(1346, 344) |  846
+ (369, 1457),(278, 1409) |  909
+(5 rows)
+
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
+(15 rows)
+
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
+(15 rows)
+
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
-              c               
-------------------------------
- (30746, 50040, 30727, 49992)
- (36311, 50073, 36258, 49987)
- (3531, 49962, 3463, 49934)
- (17954, 49975, 17865, 49915)
- (2168, 50012, 2108, 49914)
- (31287, 49923, 31236, 49913)
- (21551, 49983, 21492, 49885)
- (43925, 49912, 43888, 49878)
- (19128, 49932, 19112, 49849)
- (38266, 49852, 38233, 49844)
- (14913, 49873, 14849, 49836)
- (37595, 49849, 37581, 49834)
- (46151, 49848, 46058, 49830)
- (29261, 49910, 29247, 49818)
- (19233, 49824, 19185, 49794)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
+RESET enable_indexscan;
diff --git a/contrib/cube/expected/cube_2.out b/contrib/cube/expected/cube_2.out
index b979c4d6c8..8c75e27b46 100644
--- a/contrib/cube/expected/cube_2.out
+++ b/contrib/cube/expected/cube_2.out
@@ -1532,25 +1532,25 @@ SELECT cube(array[40,50,60], array[10,20,30])~>1;
 SELECT cube(array[10,20,30], array[40,50,60])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[10,20,30], array[40,50,60])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>0;
@@ -1558,7 +1558,7 @@ ERROR:  cube index 0 is out of bounds
 SELECT cube(array[40,50,60], array[10,20,30])~>4;
  ?column? 
 ----------
-       40
+       50
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
@@ -1611,25 +1611,28 @@ SELECT c FROM test_cube WHERE c <@ '(3000,1000),(0,0)' ORDER BY c;
 (4 rows)
 
 RESET enable_bitmapscan;
--- kNN with index
+-- Test kNN
+INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
+SET enable_seqscan = false;
+-- Test different metrics
 SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            |       dist       
 -------------------------+------------------
  (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
  (759, 187),(662, 163)   |              162
  (948, 1201),(907, 1156) | 772.000647668122
  (1444, 403),(1346, 344) |              846
- (369, 1457),(278, 1409) |              909
 (5 rows)
 
 SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            | dist 
 -------------------------+------
  (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
  (759, 187),(662, 163)   |  162
  (948, 1201),(907, 1156) |  656
  (1444, 403),(1346, 344) |  846
- (369, 1457),(278, 1409) |  909
 (5 rows)
 
 SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
@@ -1637,133 +1640,203 @@ SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c
 -------------------------+------
  (337, 455),(240, 359)   |    0
  (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
  (1444, 403),(1346, 344) |  846
  (369, 1457),(278, 1409) |  909
- (948, 1201),(907, 1156) | 1063
 (5 rows)
 
--- kNN-based sorting
-SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
-             c             
----------------------------
- (54, 38679),(3, 38602)
- (83, 10271),(15, 10265)
- (122, 46832),(64, 46762)
- (167, 17214),(92, 17184)
- (161, 24465),(107, 24374)
- (162, 26040),(120, 25963)
- (154, 4019),(138, 3990)
- (259, 1850),(175, 1820)
- (207, 40886),(179, 40879)
- (288, 49588),(204, 49571)
- (270, 32616),(226, 32607)
- (318, 31489),(235, 31404)
- (337, 455),(240, 359)
- (270, 29508),(264, 29440)
- (369, 1457),(278, 1409)
+-- Test sorting by coordinates
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
-             c             
----------------------------
- (30333, 50),(30273, 6)
- (43301, 75),(43227, 43)
- (19650, 142),(19630, 51)
- (2424, 160),(2424, 81)
- (3449, 171),(3354, 108)
- (18037, 155),(17941, 109)
- (28511, 208),(28479, 114)
- (19946, 217),(19941, 118)
- (16906, 191),(16816, 139)
- (759, 187),(662, 163)
- (22684, 266),(22656, 181)
- (24423, 255),(24360, 213)
- (45989, 249),(45910, 222)
- (11399, 377),(11360, 294)
- (12162, 389),(12103, 309)
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
-               c               
--------------------------------
- (50027, 49230),(49951, 49214)
- (49980, 35004),(49937, 34963)
- (49985, 6436),(49927, 6338)
- (49999, 27218),(49908, 27176)
- (49954, 1340),(49905, 1294)
- (49944, 25163),(49902, 25153)
- (49981, 34876),(49898, 34786)
- (49957, 43390),(49897, 43384)
- (49853, 18504),(49848, 18503)
- (49902, 41752),(49818, 41746)
- (49907, 30225),(49810, 30158)
- (49843, 5175),(49808, 5145)
- (49887, 24274),(49805, 24184)
- (49847, 7128),(49798, 7067)
- (49820, 7990),(49771, 7967)
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
-               c               
--------------------------------
- (36311, 50073),(36258, 49987)
- (30746, 50040),(30727, 49992)
- (2168, 50012),(2108, 49914)
- (21551, 49983),(21492, 49885)
- (17954, 49975),(17865, 49915)
- (3531, 49962),(3463, 49934)
- (19128, 49932),(19112, 49849)
- (31287, 49923),(31236, 49913)
- (43925, 49912),(43888, 49878)
- (29261, 49910),(29247, 49818)
- (14913, 49873),(14849, 49836)
- (20007, 49858),(19921, 49778)
- (38266, 49852),(38233, 49844)
- (37595, 49849),(37581, 49834)
- (46151, 49848),(46058, 49830)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
--- same thing for index with points
-CREATE TABLE test_point(c cube);
-INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
-CREATE INDEX ON test_point USING gist(c);
-SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
-            c             
---------------------------
- (54, 38679, 3, 38602)
- (83, 10271, 15, 10265)
- (122, 46832, 64, 46762)
- (154, 4019, 138, 3990)
- (161, 24465, 107, 24374)
- (162, 26040, 120, 25963)
- (167, 17214, 92, 17184)
- (207, 40886, 179, 40879)
- (259, 1850, 175, 1820)
- (270, 29508, 264, 29440)
- (270, 32616, 226, 32607)
- (288, 49588, 204, 49571)
- (318, 31489, 235, 31404)
- (326, 18837, 285, 18817)
- (337, 455, 240, 359)
+-- Same queries with sequential scan (should give the same results as above)
+RESET enable_seqscan;
+SET enable_indexscan = OFF;
+SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            |       dist       
+-------------------------+------------------
+ (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
+ (759, 187),(662, 163)   |              162
+ (948, 1201),(907, 1156) | 772.000647668122
+ (1444, 403),(1346, 344) |              846
+(5 rows)
+
+SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
+ (759, 187),(662, 163)   |  162
+ (948, 1201),(907, 1156) |  656
+ (1444, 403),(1346, 344) |  846
+(5 rows)
+
+SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
+ (1444, 403),(1346, 344) |  846
+ (369, 1457),(278, 1409) |  909
+(5 rows)
+
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
+(15 rows)
+
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
+(15 rows)
+
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
-              c               
-------------------------------
- (30746, 50040, 30727, 49992)
- (36311, 50073, 36258, 49987)
- (3531, 49962, 3463, 49934)
- (17954, 49975, 17865, 49915)
- (2168, 50012, 2108, 49914)
- (31287, 49923, 31236, 49913)
- (21551, 49983, 21492, 49885)
- (43925, 49912, 43888, 49878)
- (19128, 49932, 19112, 49849)
- (38266, 49852, 38233, 49844)
- (14913, 49873, 14849, 49836)
- (37595, 49849, 37581, 49834)
- (46151, 49848, 46058, 49830)
- (29261, 49910, 29247, 49818)
- (19233, 49824, 19185, 49794)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
+RESET enable_indexscan;
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
index eb24576895..efa1dbe9e8 100644
--- a/contrib/cube/sql/cube.sql
+++ b/contrib/cube/sql/cube.sql
@@ -389,20 +389,29 @@ SELECT c FROM test_cube WHERE c <@ '(3000,1000),(0,0)' ORDER BY c;
 SELECT c FROM test_cube WHERE c <@ '(3000,1000),(0,0)' ORDER BY c;
 RESET enable_bitmapscan;
 
--- kNN with index
+-- Test kNN
+INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
+SET enable_seqscan = false;
+
+-- Test different metrics
 SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
 SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
 SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
 
--- kNN-based sorting
-SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
-SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
-SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
-SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
-
--- same thing for index with points
-CREATE TABLE test_point(c cube);
-INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
-CREATE INDEX ON test_point USING gist(c);
-SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
-SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
+-- Test sorting by coordinates
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+
+-- Same queries with sequential scan (should give the same results as above)
+RESET enable_seqscan;
+SET enable_indexscan = OFF;
+SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
+SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
+SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+RESET enable_indexscan;
diff --git a/doc/src/sgml/cube.sgml b/doc/src/sgml/cube.sgml
index b995dc7e2a..9cda8cac97 100644
--- a/doc/src/sgml/cube.sgml
+++ b/doc/src/sgml/cube.sgml
@@ -186,10 +186,11 @@
       <entry><literal>a ~&gt; n</literal></entry>
       <entry><type>float8</type></entry>
       <entry>
-        Get <replaceable>n</replaceable>-th coordinate in <quote>normalized</quote> cube
-        representation, in which the coordinates have been rearranged into
-        the form <quote>lower left &mdash; upper right</quote>; that is, the
-        smaller endpoint along each dimension appears first.
+        Get <replaceable>n</replaceable>-th coordinate of cube in following way:
+        n = 2 * k - 1 means lower bound of <replaceable>k</replaceable>-th
+        dimension, n = 2 * k means upper bound of
+        <replaceable>k</replaceable>-th dimension.  This operator is designed
+        for KNN-GiST support.
       </entry>
      </row>
 
0002-cube-knn-negative-coordinate-3.patchapplication/octet-stream; name=0002-cube-knn-negative-coordinate-3.patchDownload
diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
index dcc0850aa9..d96ca1ec1f 100644
--- a/contrib/cube/cube.c
+++ b/contrib/cube/cube.c
@@ -1343,12 +1343,20 @@ g_cube_distance(PG_FUNCTION_ARGS)
 		 */
 		int			coord = PG_GETARG_INT32(1);
 		bool		isLeaf = GistPageIsLeaf(entry->page);
+		bool		inverse = false;
 
 		/* 0 is the only unsupported coordinate value */
-		if (coord <= 0)
+		if (coord == 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
-					 errmsg("cube index %d is out of bounds", coord)));
+					 errmsg("zero cube index is not defined")));
+
+		/* Return inversed value for negative coordinate */
+		if (coord < 0)
+		{
+			coord = -coord;
+			inverse = true;
+		}
 
 		if (coord <= 2 * DIM(cube))
 		{
@@ -1376,9 +1384,14 @@ g_cube_distance(PG_FUNCTION_ARGS)
 					/*
 					 * For non-leaf we should always return lower bound,
 					 * because even upper bound of a child in the subtree can
-					 * be as small as our lower bound.
+					 * be as small as our lower bound.  For inversed case we
+					 * return upper bound because it becomes lower bound for
+					 * inversed value.
 					 */
-					retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
+					if (!inverse)
+						retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
+					else
+						retval = Max(cube->x[index], cube->x[index + DIM(cube)]);
 				}
 			}
 		}
@@ -1386,6 +1399,10 @@ g_cube_distance(PG_FUNCTION_ARGS)
 		{
 			retval = 0.0;
 		}
+
+		/* Inverse return value if needed */
+		if (inverse)
+			retval = -retval;
 	}
 	else
 	{
@@ -1542,11 +1559,15 @@ cube_coord(PG_FUNCTION_ARGS)
  * getter.  Moreover, indexed dataset may contain cubes of different dimensions
  * number.  Accordingly, this coordinate getter should be able to return
  * lower/upper bound for particular dimension independently on number of cube
- * dimensions.
+ * dimensions.  Also, KNN-GiST supports only ascending sorting.  In order to
+ * support descending sorting, this function returns inverse of value when
+ * negative coordinate is given.
  *
  * Long story short, this function uses following meaning of coordinates:
  * # (2 * N - 1) -- lower bound of Nth dimension,
- * # (2 * N) -- upper bound of Nth dimension.
+ * # (2 * N) -- upper bound of Nth dimension,
+ * # - (2 * N - 1) -- negative of lower bound of Nth dimension,
+ * # - (2 * N) -- negative of upper bound of Nth dimension.
  *
  * When given coordinate exceeds number of cube dimensions, then 0 returned
  * (reproducing logic of GiST indexing of variable-length cubes).
@@ -1560,10 +1581,17 @@ cube_coord_llur(PG_FUNCTION_ARGS)
 	float8		result;
 
 	/* 0 is the only unsupported coordinate value */
-	if (coord <= 0)
+	if (coord == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
-				 errmsg("cube index %d is out of bounds", coord)));
+				 errmsg("zero cube index is not defined")));
+
+	/* Return inversed value for negative coordinate */
+	if (coord < 0)
+	{
+		coord = -coord;
+		inverse = true;
+	}
 
 	if (coord <= 2 * DIM(cube))
 	{
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
index c586a73727..6378db3004 100644
--- a/contrib/cube/expected/cube.out
+++ b/contrib/cube/expected/cube.out
@@ -1554,7 +1554,7 @@ SELECT cube(array[40,50,60], array[10,20,30])~>3;
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>0;
-ERROR:  cube index 0 is out of bounds
+ERROR:  zero cube index is not defined
 SELECT cube(array[40,50,60], array[10,20,30])~>4;
  ?column? 
 ----------
@@ -1562,7 +1562,11 @@ SELECT cube(array[40,50,60], array[10,20,30])~>4;
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
-ERROR:  cube index -1 is out of bounds
+ ?column? 
+----------
+      -10
+(1 row)
+
 -- Load some example data and build the index
 --
 CREATE TABLE test_cube (c cube);
@@ -1726,6 +1730,86 @@ SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper boun
       266 | (22684, 266),(22656, 181)
 (15 rows)
 
+SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (100000)
+   -49951 | (50027, 49230),(49951, 49214)
+   -49937 | (49980, 35004),(49937, 34963)
+   -49927 | (49985, 6436),(49927, 6338)
+   -49908 | (49999, 27218),(49908, 27176)
+   -49905 | (49954, 1340),(49905, 1294)
+   -49902 | (49944, 25163),(49902, 25153)
+   -49898 | (49981, 34876),(49898, 34786)
+   -49897 | (49957, 43390),(49897, 43384)
+   -49848 | (49853, 18504),(49848, 18503)
+   -49818 | (49902, 41752),(49818, 41746)
+   -49810 | (49907, 30225),(49810, 30158)
+   -49808 | (49843, 5175),(49808, 5145)
+   -49805 | (49887, 24274),(49805, 24184)
+   -49798 | (49847, 7128),(49798, 7067)
+(15 rows)
+
+SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (100000)
+   -50027 | (50027, 49230),(49951, 49214)
+   -49999 | (49999, 27218),(49908, 27176)
+   -49985 | (49985, 6436),(49927, 6338)
+   -49981 | (49981, 34876),(49898, 34786)
+   -49980 | (49980, 35004),(49937, 34963)
+   -49957 | (49957, 43390),(49897, 43384)
+   -49954 | (49954, 1340),(49905, 1294)
+   -49944 | (49944, 25163),(49902, 25153)
+   -49907 | (49907, 30225),(49810, 30158)
+   -49902 | (49902, 41752),(49818, 41746)
+   -49887 | (49887, 24274),(49805, 24184)
+   -49853 | (49853, 18504),(49848, 18503)
+   -49847 | (49847, 7128),(49798, 7067)
+   -49843 | (49843, 5175),(49808, 5145)
+(15 rows)
+
+SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (0, 100000)
+   -49992 | (30746, 50040),(30727, 49992)
+   -49987 | (36311, 50073),(36258, 49987)
+   -49934 | (3531, 49962),(3463, 49934)
+   -49915 | (17954, 49975),(17865, 49915)
+   -49914 | (2168, 50012),(2108, 49914)
+   -49913 | (31287, 49923),(31236, 49913)
+   -49885 | (21551, 49983),(21492, 49885)
+   -49878 | (43925, 49912),(43888, 49878)
+   -49849 | (19128, 49932),(19112, 49849)
+   -49844 | (38266, 49852),(38233, 49844)
+   -49836 | (14913, 49873),(14849, 49836)
+   -49834 | (37595, 49849),(37581, 49834)
+   -49830 | (46151, 49848),(46058, 49830)
+   -49818 | (29261, 49910),(29247, 49818)
+(15 rows)
+
+SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (0, 100000)
+   -50073 | (36311, 50073),(36258, 49987)
+   -50040 | (30746, 50040),(30727, 49992)
+   -50012 | (2168, 50012),(2108, 49914)
+   -49983 | (21551, 49983),(21492, 49885)
+   -49975 | (17954, 49975),(17865, 49915)
+   -49962 | (3531, 49962),(3463, 49934)
+   -49932 | (19128, 49932),(19112, 49849)
+   -49923 | (31287, 49923),(31236, 49913)
+   -49912 | (43925, 49912),(43888, 49878)
+   -49910 | (29261, 49910),(29247, 49818)
+   -49873 | (14913, 49873),(14849, 49836)
+   -49858 | (20007, 49858),(19921, 49778)
+   -49852 | (38266, 49852),(38233, 49844)
+   -49849 | (37595, 49849),(37581, 49834)
+(15 rows)
+
 -- Same queries with sequential scan (should give the same results as above)
 RESET enable_seqscan;
 SET enable_indexscan = OFF;
@@ -1839,4 +1923,84 @@ SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper boun
       266 | (22684, 266),(22656, 181)
 (15 rows)
 
+SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (100000)
+   -49951 | (50027, 49230),(49951, 49214)
+   -49937 | (49980, 35004),(49937, 34963)
+   -49927 | (49985, 6436),(49927, 6338)
+   -49908 | (49999, 27218),(49908, 27176)
+   -49905 | (49954, 1340),(49905, 1294)
+   -49902 | (49944, 25163),(49902, 25153)
+   -49898 | (49981, 34876),(49898, 34786)
+   -49897 | (49957, 43390),(49897, 43384)
+   -49848 | (49853, 18504),(49848, 18503)
+   -49818 | (49902, 41752),(49818, 41746)
+   -49810 | (49907, 30225),(49810, 30158)
+   -49808 | (49843, 5175),(49808, 5145)
+   -49805 | (49887, 24274),(49805, 24184)
+   -49798 | (49847, 7128),(49798, 7067)
+(15 rows)
+
+SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (100000)
+   -50027 | (50027, 49230),(49951, 49214)
+   -49999 | (49999, 27218),(49908, 27176)
+   -49985 | (49985, 6436),(49927, 6338)
+   -49981 | (49981, 34876),(49898, 34786)
+   -49980 | (49980, 35004),(49937, 34963)
+   -49957 | (49957, 43390),(49897, 43384)
+   -49954 | (49954, 1340),(49905, 1294)
+   -49944 | (49944, 25163),(49902, 25153)
+   -49907 | (49907, 30225),(49810, 30158)
+   -49902 | (49902, 41752),(49818, 41746)
+   -49887 | (49887, 24274),(49805, 24184)
+   -49853 | (49853, 18504),(49848, 18503)
+   -49847 | (49847, 7128),(49798, 7067)
+   -49843 | (49843, 5175),(49808, 5145)
+(15 rows)
+
+SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (0, 100000)
+   -49992 | (30746, 50040),(30727, 49992)
+   -49987 | (36311, 50073),(36258, 49987)
+   -49934 | (3531, 49962),(3463, 49934)
+   -49915 | (17954, 49975),(17865, 49915)
+   -49914 | (2168, 50012),(2108, 49914)
+   -49913 | (31287, 49923),(31236, 49913)
+   -49885 | (21551, 49983),(21492, 49885)
+   -49878 | (43925, 49912),(43888, 49878)
+   -49849 | (19128, 49932),(19112, 49849)
+   -49844 | (38266, 49852),(38233, 49844)
+   -49836 | (14913, 49873),(14849, 49836)
+   -49834 | (37595, 49849),(37581, 49834)
+   -49830 | (46151, 49848),(46058, 49830)
+   -49818 | (29261, 49910),(29247, 49818)
+(15 rows)
+
+SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (0, 100000)
+   -50073 | (36311, 50073),(36258, 49987)
+   -50040 | (30746, 50040),(30727, 49992)
+   -50012 | (2168, 50012),(2108, 49914)
+   -49983 | (21551, 49983),(21492, 49885)
+   -49975 | (17954, 49975),(17865, 49915)
+   -49962 | (3531, 49962),(3463, 49934)
+   -49932 | (19128, 49932),(19112, 49849)
+   -49923 | (31287, 49923),(31236, 49913)
+   -49912 | (43925, 49912),(43888, 49878)
+   -49910 | (29261, 49910),(29247, 49818)
+   -49873 | (14913, 49873),(14849, 49836)
+   -49858 | (20007, 49858),(19921, 49778)
+   -49852 | (38266, 49852),(38233, 49844)
+   -49849 | (37595, 49849),(37581, 49834)
+(15 rows)
+
 RESET enable_indexscan;
diff --git a/contrib/cube/expected/cube_2.out b/contrib/cube/expected/cube_2.out
index 8c75e27b46..75fe405c49 100644
--- a/contrib/cube/expected/cube_2.out
+++ b/contrib/cube/expected/cube_2.out
@@ -1554,7 +1554,7 @@ SELECT cube(array[40,50,60], array[10,20,30])~>3;
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>0;
-ERROR:  cube index 0 is out of bounds
+ERROR:  zero cube index is not defined
 SELECT cube(array[40,50,60], array[10,20,30])~>4;
  ?column? 
 ----------
@@ -1562,7 +1562,11 @@ SELECT cube(array[40,50,60], array[10,20,30])~>4;
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
-ERROR:  cube index -1 is out of bounds
+ ?column? 
+----------
+      -10
+(1 row)
+
 -- Load some example data and build the index
 --
 CREATE TABLE test_cube (c cube);
@@ -1726,6 +1730,86 @@ SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper boun
       266 | (22684, 266),(22656, 181)
 (15 rows)
 
+SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (100000)
+   -49951 | (50027, 49230),(49951, 49214)
+   -49937 | (49980, 35004),(49937, 34963)
+   -49927 | (49985, 6436),(49927, 6338)
+   -49908 | (49999, 27218),(49908, 27176)
+   -49905 | (49954, 1340),(49905, 1294)
+   -49902 | (49944, 25163),(49902, 25153)
+   -49898 | (49981, 34876),(49898, 34786)
+   -49897 | (49957, 43390),(49897, 43384)
+   -49848 | (49853, 18504),(49848, 18503)
+   -49818 | (49902, 41752),(49818, 41746)
+   -49810 | (49907, 30225),(49810, 30158)
+   -49808 | (49843, 5175),(49808, 5145)
+   -49805 | (49887, 24274),(49805, 24184)
+   -49798 | (49847, 7128),(49798, 7067)
+(15 rows)
+
+SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (100000)
+   -50027 | (50027, 49230),(49951, 49214)
+   -49999 | (49999, 27218),(49908, 27176)
+   -49985 | (49985, 6436),(49927, 6338)
+   -49981 | (49981, 34876),(49898, 34786)
+   -49980 | (49980, 35004),(49937, 34963)
+   -49957 | (49957, 43390),(49897, 43384)
+   -49954 | (49954, 1340),(49905, 1294)
+   -49944 | (49944, 25163),(49902, 25153)
+   -49907 | (49907, 30225),(49810, 30158)
+   -49902 | (49902, 41752),(49818, 41746)
+   -49887 | (49887, 24274),(49805, 24184)
+   -49853 | (49853, 18504),(49848, 18503)
+   -49847 | (49847, 7128),(49798, 7067)
+   -49843 | (49843, 5175),(49808, 5145)
+(15 rows)
+
+SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (0, 100000)
+   -49992 | (30746, 50040),(30727, 49992)
+   -49987 | (36311, 50073),(36258, 49987)
+   -49934 | (3531, 49962),(3463, 49934)
+   -49915 | (17954, 49975),(17865, 49915)
+   -49914 | (2168, 50012),(2108, 49914)
+   -49913 | (31287, 49923),(31236, 49913)
+   -49885 | (21551, 49983),(21492, 49885)
+   -49878 | (43925, 49912),(43888, 49878)
+   -49849 | (19128, 49932),(19112, 49849)
+   -49844 | (38266, 49852),(38233, 49844)
+   -49836 | (14913, 49873),(14849, 49836)
+   -49834 | (37595, 49849),(37581, 49834)
+   -49830 | (46151, 49848),(46058, 49830)
+   -49818 | (29261, 49910),(29247, 49818)
+(15 rows)
+
+SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (0, 100000)
+   -50073 | (36311, 50073),(36258, 49987)
+   -50040 | (30746, 50040),(30727, 49992)
+   -50012 | (2168, 50012),(2108, 49914)
+   -49983 | (21551, 49983),(21492, 49885)
+   -49975 | (17954, 49975),(17865, 49915)
+   -49962 | (3531, 49962),(3463, 49934)
+   -49932 | (19128, 49932),(19112, 49849)
+   -49923 | (31287, 49923),(31236, 49913)
+   -49912 | (43925, 49912),(43888, 49878)
+   -49910 | (29261, 49910),(29247, 49818)
+   -49873 | (14913, 49873),(14849, 49836)
+   -49858 | (20007, 49858),(19921, 49778)
+   -49852 | (38266, 49852),(38233, 49844)
+   -49849 | (37595, 49849),(37581, 49834)
+(15 rows)
+
 -- Same queries with sequential scan (should give the same results as above)
 RESET enable_seqscan;
 SET enable_indexscan = OFF;
@@ -1839,4 +1923,84 @@ SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper boun
       266 | (22684, 266),(22656, 181)
 (15 rows)
 
+SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (100000)
+   -49951 | (50027, 49230),(49951, 49214)
+   -49937 | (49980, 35004),(49937, 34963)
+   -49927 | (49985, 6436),(49927, 6338)
+   -49908 | (49999, 27218),(49908, 27176)
+   -49905 | (49954, 1340),(49905, 1294)
+   -49902 | (49944, 25163),(49902, 25153)
+   -49898 | (49981, 34876),(49898, 34786)
+   -49897 | (49957, 43390),(49897, 43384)
+   -49848 | (49853, 18504),(49848, 18503)
+   -49818 | (49902, 41752),(49818, 41746)
+   -49810 | (49907, 30225),(49810, 30158)
+   -49808 | (49843, 5175),(49808, 5145)
+   -49805 | (49887, 24274),(49805, 24184)
+   -49798 | (49847, 7128),(49798, 7067)
+(15 rows)
+
+SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (100000)
+   -50027 | (50027, 49230),(49951, 49214)
+   -49999 | (49999, 27218),(49908, 27176)
+   -49985 | (49985, 6436),(49927, 6338)
+   -49981 | (49981, 34876),(49898, 34786)
+   -49980 | (49980, 35004),(49937, 34963)
+   -49957 | (49957, 43390),(49897, 43384)
+   -49954 | (49954, 1340),(49905, 1294)
+   -49944 | (49944, 25163),(49902, 25153)
+   -49907 | (49907, 30225),(49810, 30158)
+   -49902 | (49902, 41752),(49818, 41746)
+   -49887 | (49887, 24274),(49805, 24184)
+   -49853 | (49853, 18504),(49848, 18503)
+   -49847 | (49847, 7128),(49798, 7067)
+   -49843 | (49843, 5175),(49808, 5145)
+(15 rows)
+
+SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (0, 100000)
+   -49992 | (30746, 50040),(30727, 49992)
+   -49987 | (36311, 50073),(36258, 49987)
+   -49934 | (3531, 49962),(3463, 49934)
+   -49915 | (17954, 49975),(17865, 49915)
+   -49914 | (2168, 50012),(2108, 49914)
+   -49913 | (31287, 49923),(31236, 49913)
+   -49885 | (21551, 49983),(21492, 49885)
+   -49878 | (43925, 49912),(43888, 49878)
+   -49849 | (19128, 49932),(19112, 49849)
+   -49844 | (38266, 49852),(38233, 49844)
+   -49836 | (14913, 49873),(14849, 49836)
+   -49834 | (37595, 49849),(37581, 49834)
+   -49830 | (46151, 49848),(46058, 49830)
+   -49818 | (29261, 49910),(29247, 49818)
+(15 rows)
+
+SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
+ ?column? |               c               
+----------+-------------------------------
+  -100000 | (0, 100000)
+   -50073 | (36311, 50073),(36258, 49987)
+   -50040 | (30746, 50040),(30727, 49992)
+   -50012 | (2168, 50012),(2108, 49914)
+   -49983 | (21551, 49983),(21492, 49885)
+   -49975 | (17954, 49975),(17865, 49915)
+   -49962 | (3531, 49962),(3463, 49934)
+   -49932 | (19128, 49932),(19112, 49849)
+   -49923 | (31287, 49923),(31236, 49913)
+   -49912 | (43925, 49912),(43888, 49878)
+   -49910 | (29261, 49910),(29247, 49818)
+   -49873 | (14913, 49873),(14849, 49836)
+   -49858 | (20007, 49858),(19921, 49778)
+   -49852 | (38266, 49852),(38233, 49844)
+   -49849 | (37595, 49849),(37581, 49834)
+(15 rows)
+
 RESET enable_indexscan;
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
index efa1dbe9e8..f599e7f7c0 100644
--- a/contrib/cube/sql/cube.sql
+++ b/contrib/cube/sql/cube.sql
@@ -403,6 +403,10 @@ SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
 SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
 SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
 SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
 
 -- Same queries with sequential scan (should give the same results as above)
 RESET enable_seqscan;
@@ -414,4 +418,8 @@ SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
 SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
 SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
 SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound
+SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound
+SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound
+SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound
 RESET enable_indexscan;
diff --git a/doc/src/sgml/cube.sgml b/doc/src/sgml/cube.sgml
index 9cda8cac97..e010305d84 100644
--- a/doc/src/sgml/cube.sgml
+++ b/doc/src/sgml/cube.sgml
@@ -189,8 +189,9 @@
         Get <replaceable>n</replaceable>-th coordinate of cube in following way:
         n = 2 * k - 1 means lower bound of <replaceable>k</replaceable>-th
         dimension, n = 2 * k means upper bound of
-        <replaceable>k</replaceable>-th dimension.  This operator is designed
-        for KNN-GiST support.
+        <replaceable>k</replaceable>-th dimension.  Negative
+        <replaceable>n</replaceable> denotes inversed value of corresponding
+        positive coordinate.  This operator is designed for KNN-GiST support.
       </entry>
      </row>
 
#15Andrey Borodin
x4mmm@yandex-team.ru
In reply to: Alexander Korotkov (#14)
Re: CUBE seems a bit confused about ORDER BY

The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: tested, passed

Hi! I've reviewed the patch.
Patch works as expected, documented and tested.

The code does not use LL_COORD and UR_COORD used all around. But the code still is easily readable.

LL_COORD and UR_COORD are always combined with Min or Max function, so LL (Lower Left) and UR (Upper Right) are somewhat irrelevant names...
BTW if we swap implementation of these macro-funcs and fix cube_out, almost all tests pass again. This is evidence of good compatibility and shows that compatibility overhead is added everywhere.

I think that this patch is ready for committer.

Best regards, Andrey Borodin.

The new status of this patch is: Ready for Committer

#16Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Andrey Borodin (#15)
Re: CUBE seems a bit confused about ORDER BY

Hi!

On Fri, Dec 8, 2017 at 11:21 AM, Andrey Borodin <x4mmm@yandex-team.ru>
wrote:

The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: tested, passed

Hi! I've reviewed the patch.
Patch works as expected, documented and tested.

Thank you for reviewing this.

The code does not use LL_COORD and UR_COORD used all around. But the code
still is easily readable.

LL_COORD and UR_COORD are always combined with Min or Max function, so LL
(Lower Left) and UR (Upper Right) are somewhat irrelevant names...
BTW if we swap implementation of these macro-funcs and fix cube_out,
almost all tests pass again. This is evidence of good compatibility and
shows that compatibility overhead is added everywhere.

Yes, the thing is that we change behavior of existing ~> operator. In
general, this is not good idea because it could affect existing users whose
already use this operator. Typically in such situation, we could leave
existing operator as is, and invent new operator with new behavior.
However, in this particular case, I think there are reasons to make an
exception to the rules. The reasons are following:
1) The ~> operator was designed especially for knn gist.
2) Knn gist support for current behavior is broken by design and can't be
fixed. Most we can do to fix existing ~> operator behavior as is to drop
knn gist support. But then ~> operator would be left useless.
3) It doesn't seems that ~> operator have many users yet, because an error
wasn't reported during whole release cycle.

I hope these reasons justify altering behavior of existing operator as an
exception to the rules. Another question is whether we should backpatch
it. But I think we could leave this decision to committer.

I think that this patch is ready for committer.

Good.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#17Teodor Sigaev
teodor@sigaev.ru
In reply to: Alexander Korotkov (#16)
Re: CUBE seems a bit confused about ORDER BY

Yes, the thing is that we change behavior of existing ~> operator.О©╫ In general,
this is not good idea because it could affect existing users whose already use
this operator.О©╫ Typically in such situation, we could leave existing operator as
is, and invent new operator with new behavior.О©╫ However, in this particular
case, I think there are reasons to make an exception to the rules.О©╫ The reasons
are following:
1) The ~> operator was designed especially for knn gist.
2) Knn gist support for current behavior is broken by design and can't be
fixed.О©╫ Most we can do to fix existing ~> operator behavior as is to drop knn
gist support.О©╫ But then ~> operator would be left useless.
3) It doesn't seems that ~> operator have many users yet, because an error
wasn't reported during whole release cycle.

I hope these reasons justify altering behavior of existing operator as an
exception to the rules.О©╫ Another question is whether we should backpatch it.
But I think we could leave this decision to committer.

I think that this patch is ready for committer.

I'm agree with changing behavior of existing ~> operator, but is any objection
here? Current implementation is not fixable and I hope that users which use this
operator will understand why we change it. Fortunately, the fix doesn't require
changes in system catalog.

The single question here is about index over expression with this operator, they
will need to reindex, which should be noted in release notes.

--
Teodor Sigaev E-mail: teodor@sigaev.ru
WWW: http://www.sigaev.ru/

#18Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Teodor Sigaev (#17)
Re: CUBE seems a bit confused about ORDER BY

On Tue, Dec 12, 2017 at 3:49 PM, Teodor Sigaev <teodor@sigaev.ru> wrote:

Yes, the thing is that we change behavior of existing ~> operator. In

general, this is not good idea because it could affect existing users whose
already use this operator. Typically in such situation, we could leave
existing operator as is, and invent new operator with new behavior.
However, in this particular case, I think there are reasons to make an
exception to the rules. The reasons are following:
1) The ~> operator was designed especially for knn gist.
2) Knn gist support for current behavior is broken by design and can't be
fixed. Most we can do to fix existing ~> operator behavior as is to drop
knn gist support. But then ~> operator would be left useless.
3) It doesn't seems that ~> operator have many users yet, because an
error wasn't reported during whole release cycle.

I hope these reasons justify altering behavior of existing operator as an
exception to the rules. Another question is whether we should backpatch
it. But I think we could leave this decision to committer.

I think that this patch is ready for committer.

I'm agree with changing behavior of existing ~> operator, but is any
objection here? Current implementation is not fixable and I hope that users
which use this operator will understand why we change it. Fortunately, the
fix doesn't require changes in system catalog.

The single question here is about index over expression with this
operator, they will need to reindex, which should be noted in release notes.

Yes. I bet only few users have built indexes over ~> operator if any. Ask
them to reindex in the release notes seems OK for me.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#19Tomas Vondra
tomas.vondra@2ndquadrant.com
In reply to: Alexander Korotkov (#18)
Re: CUBE seems a bit confused about ORDER BY

On 12/12/2017 01:52 PM, Alexander Korotkov wrote:

On Tue, Dec 12, 2017 at 3:49 PM, Teodor Sigaev <teodor@sigaev.ru
<mailto:teodor@sigaev.ru>> wrote:

Yes, the thing is that we change behavior of existing ~>
operator.  In general, this is not good idea because it could
affect existing users whose already use this operator. 
Typically in such situation, we could leave existing operator as
is, and invent new operator with new behavior.  However, in this
particular case, I think there are reasons to make an exception
to the rules.  The reasons are following:
1) The ~> operator was designed especially for knn gist.
2) Knn gist support for current behavior is broken by design and
can't be fixed.  Most we can do to fix existing ~> operator
behavior as is to drop knn gist support.  But then ~> operator
would be left useless.
3) It doesn't seems that ~> operator have many users yet,
because an error wasn't reported during whole release cycle.

I hope these reasons justify altering behavior of existing
operator as an exception to the rules.  Another question is
whether we should backpatch it.  But I think we could leave this
decision to committer.

    I think that this patch is ready for committer.

I'm agree with changing behavior of existing ~> operator, but is any
objection here? Current implementation is not fixable and I hope
that users which use this operator will understand why we change it.
Fortunately, the fix doesn't require changes in system catalog.

The single question here is about index over expression with this
operator, they will need to reindex, which should be noted in
release notes.

Yes.  I bet only few users have built indexes over ~> operator if any. 
Ask them to reindex in the release notes seems OK for me.

Is there a good way to detect such cases? Either in pg_upgrade, so that
we can print warnings, or at least manually (which would be suitable for
release notes).

regards

--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#20Teodor Sigaev
teodor@sigaev.ru
In reply to: Tomas Vondra (#19)
Re: CUBE seems a bit confused about ORDER BY

Yes.О©╫ I bet only few users have built indexes over ~> operator if any.
Ask them to reindex in the release notes seems OK for me.

Is there a good way to detect such cases? Either in pg_upgrade, so that
we can print warnings, or at least manually (which would be suitable for
release notes).

Hmm, suppose, fix should be backpatched (because now it's unusable) and
pg_upgrade should not do anything. Just add release note to 10.0 and 11.0

Oh, check expression is affected too, users will need to reinsert data.

--
Teodor Sigaev E-mail: teodor@sigaev.ru
WWW: http://www.sigaev.ru/

#21Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Teodor Sigaev (#20)
Re: CUBE seems a bit confused about ORDER BY

On Thu, Dec 14, 2017 at 1:36 PM, Teodor Sigaev <teodor@sigaev.ru> wrote:

Yes. I bet only few users have built indexes over ~> operator if any.

Ask them to reindex in the release notes seems OK for me.

Is there a good way to detect such cases? Either in pg_upgrade, so that
we can print warnings, or at least manually (which would be suitable for
release notes).

Hmm, suppose, fix should be backpatched (because now it's unusable) and
pg_upgrade should not do anything. Just add release note to 10.0 and 11.0

Oh, check expression is affected too, users will need to reinsert data.

I wrote query to find both constraints and indexes depending on ~> cube
operator.

SELECT dep.classid::regclass AS class,
CASE WHEN dep.classid = 'pg_catalog.pg_class'::regclass THEN
dep.objid::regclass::text
WHEN dep.classid = 'pg_catalog.pg_constraint'::regclass THEN (SELECT
conname FROM pg_catalog.pg_constraint WHERE oid = dep.objid)
ELSE NULL
END AS name
FROM
pg_catalog.pg_extension e
JOIN pg_catalog.pg_depend edep ON edep.refclassid =
'pg_catalog.pg_extension'::regclass AND edep.refobjid = e.oid AND deptype =
'e' AND edep.classid = 'pg_catalog.pg_operator'::regclass
JOIN pg_catalog.pg_operator o ON o.oid = edep.objid AND o.oprname = '~>'
JOIN pg_catalog.pg_depend dep ON dep.refclassid =
'pg_catalog.pg_operator'::regclass AND dep.refobjid = o.oid
WHERE
e.extname = 'cube' AND dep.classid IN
('pg_catalog.pg_constraint'::regclass, 'pg_catalog.pg_class'::regclass);

On the below data schema

create table tmp (c cube, check ((c ~> 0 > 0)));
create index tmp_idx on tmp ((c~>0));

it gives following result

class | name
---------------+-------------
pg_class | tmp_idx
pg_constraint | tmp_c_check
(2 rows)

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#22Teodor Sigaev
teodor@sigaev.ru
In reply to: Alexander Korotkov (#21)
Re: CUBE seems a bit confused about ORDER BY

SELECT dep.classid::regclass AS class,
О©╫ CASE WHEN dep.classid = 'pg_catalog.pg_class'::regclass THEN
dep.objid::regclass::text
О©╫ О©╫ О©╫ О©╫WHEN dep.classid = 'pg_catalog.pg_constraint'::regclass THEN (SELECT
conname FROM pg_catalog.pg_constraint WHERE oid = dep.objid)
О©╫ ELSE NULL
О©╫ END AS name
FROM
О©╫ pg_catalog.pg_extension e
О©╫ JOIN pg_catalog.pg_depend edep ON edep.refclassid =
'pg_catalog.pg_extension'::regclass AND edep.refobjid = e.oid AND deptype = 'e'
AND edep.classid = 'pg_catalog.pg_operator'::regclass
О©╫ JOIN pg_catalog.pg_operator o ON o.oid = edep.objid AND o.oprname = '~>'
О©╫ JOIN pg_catalog.pg_depend dep ON dep.refclassid =
'pg_catalog.pg_operator'::regclass AND dep.refobjid = o.oid
WHERE
О©╫ e.extname = 'cube' AND dep.classid IN ('pg_catalog.pg_constraint'::regclass,
'pg_catalog.pg_class'::regclass);

On the below data schema

create table tmp (c cube, check ((c ~> 0 > 0)));
create index tmp_idx on tmp ((c~>0));

it gives following result

О©╫ О©╫ О©╫classО©╫ О©╫ О©╫|О©╫ О©╫ name
---------------+-------------
О©╫pg_classО©╫ О©╫ О©╫ | tmp_idx
О©╫pg_constraint | tmp_c_check
(2 rows)

SQL-query seems too huge for release notes and isn't looking for materialized
view (fixable) and functional indexes with function which contains this operator
somewhere inside (not fixable by this query). I think, just words is enough.

--
Teodor Sigaev E-mail: teodor@sigaev.ru
WWW: http://www.sigaev.ru/

#23Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Teodor Sigaev (#22)
Re: CUBE seems a bit confused about ORDER BY

On Thu, Dec 14, 2017 at 2:39 PM, Teodor Sigaev <teodor@sigaev.ru> wrote:

SQL-query seems too huge for release notes and isn't looking for
materialized view (fixable) and functional indexes with function which
contains this operator somewhere inside (not fixable by this query). I
think, just words is enough.

I found that cube_2.out expected output in regression tests is used on
Windows 2003 in my virtual machine. I've checked that for both cube
patches, regression tests pass correctly on that virtual machine.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#24Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Teodor Sigaev (#22)
Re: CUBE seems a bit confused about ORDER BY

Teodor Sigaev wrote:

SQL-query seems too huge for release notes and isn't looking for
materialized view (fixable) and functional indexes with function which
contains this operator somewhere inside (not fixable by this query). I
think, just words is enough.

But the query can be made a little bit shorter and more comprehensible:

SELECT pg_describe_object(dep.classid, dep.objid, dep.objsubid)
FROM pg_catalog.pg_extension ext
JOIN pg_catalog.pg_depend edep ON edep.refobjid = ext.oid
JOIN pg_catalog.pg_operator oper ON oper.oid = edep.objid
JOIN pg_catalog.pg_depend dep ON dep.refobjid = oper.oid
WHERE
ext.extname = 'cube' AND
edep.refclassid = 'pg_catalog.pg_extension'::regclass AND
edep.classid = 'pg_catalog.pg_operator'::regclass AND
edep.deptype = 'e' AND
oper.oprname = '~>' AND
dep.refclassid = 'pg_catalog.pg_operator'::regclass
;

which returns the following

pg_describe_object
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
regla «_RETURN» en vista materializada f
índice tmp_idx
restricción «tmp_c_check» en tabla tmp
operador 15 (cube, integer) de familia de operadores gist_cube_ops para el método de acceso gist: ~>(cube,integer)
(4 filas)

(after
create materialized view f as select * from tmp where c~>1 > 1;
)

I think this is useful enough. The fact remains that we can't check
very well for functions; maybe suggest a LIKE clause to look for ~>
anywhere in function source code?

(It looks like you could get rid of the 'deptype' qual and
dep.refclassid also)

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#25Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alvaro Herrera (#24)
2 attachment(s)
Re: CUBE seems a bit confused about ORDER BY

On Wed, Jan 10, 2018 at 8:02 PM, Alvaro Herrera <alvherre@alvh.no-ip.org>
wrote:

Teodor Sigaev wrote:

SQL-query seems too huge for release notes and isn't looking for
materialized view (fixable) and functional indexes with function which
contains this operator somewhere inside (not fixable by this query). I
think, just words is enough.

But the query can be made a little bit shorter and more comprehensible:

SELECT pg_describe_object(dep.classid, dep.objid, dep.objsubid)
FROM pg_catalog.pg_extension ext
JOIN pg_catalog.pg_depend edep ON edep.refobjid = ext.oid
JOIN pg_catalog.pg_operator oper ON oper.oid = edep.objid
JOIN pg_catalog.pg_depend dep ON dep.refobjid = oper.oid
WHERE
ext.extname = 'cube' AND
edep.refclassid = 'pg_catalog.pg_extension'::regclass AND
edep.classid = 'pg_catalog.pg_operator'::regclass AND
edep.deptype = 'e' AND
oper.oprname = '~>' AND
dep.refclassid = 'pg_catalog.pg_operator'::regclass
;

which returns the following

pg_describe_object
────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────
regla «_RETURN» en vista materializada f
índice tmp_idx
restricción «tmp_c_check» en tabla tmp
operador 15 (cube, integer) de familia de operadores gist_cube_ops para
el método de acceso gist: ~>(cube,integer)
(4 filas)

(after
create materialized view f as select * from tmp where c~>1 > 1;
)

Yes, it looks better. I didn't notice we can use pg_describe_object() here.

I think this is useful enough. The fact remains that we can't check

very well for functions; maybe suggest a LIKE clause to look for ~>
anywhere in function source code?

That's an option, but we should note that this check is inexact.

(It looks like you could get rid of the 'deptype' qual and

dep.refclassid also)

Since this bugfix should be backpatched to 9.6, there are patches for 9.6
and 10 too.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-cube-knn-fix-pg9.6.patchapplication/octet-stream; name=0001-cube-knn-fix-pg9.6.patchDownload
diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
index 3feddef8f3..6c4a5b24c1 100644
--- a/contrib/cube/cube.c
+++ b/contrib/cube/cube.c
@@ -1368,13 +1368,55 @@ g_cube_distance(PG_FUNCTION_ARGS)
 
 	if (strategy == CubeKNNDistanceCoord)
 	{
+		/*
+		 * Handle ordering by ~> operator.  See comments of cube_coord_llur()
+		 * for details
+		 */
 		int			coord = PG_GETARG_INT32(1);
+		bool		isLeaf = GistPageIsLeaf(entry->page);
 
-		if (IS_POINT(cube))
-			retval = cube->x[(coord - 1) % DIM(cube)];
+		/* 0 is the only unsupported coordinate value */
+		if (coord <= 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
+					 errmsg("cube index %d is out of bounds", coord)));
+
+		if (coord <= 2 * DIM(cube))
+		{
+			/* dimension index */
+			int		index = (coord - 1) / 2;
+			/* whether this is upper bound (lower bound otherwise) */
+			bool	upper = ((coord - 1) % 2 == 1);
+
+			if (IS_POINT(cube))
+			{
+				retval = cube->x[index];
+			}
+			else
+			{
+				if (isLeaf)
+				{
+					/* For leaf just return required upper/lower bound */
+					if (upper)
+						retval = Max(cube->x[index], cube->x[index + DIM(cube)]);
+					else
+						retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
+				}
+				else
+				{
+					/*
+					 * For non-leaf we should always return lower bound,
+					 * because even upper bound of a child in the subtree can
+					 * be as small as our lower bound.
+					 */
+					retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
+				}
+			}
+		}
 		else
-			retval = Min(cube->x[(coord - 1) % DIM(cube)],
-						 cube->x[(coord - 1) % DIM(cube) + DIM(cube)]);
+		{
+			retval = 0.0;
+		}
 	}
 	else
 	{
@@ -1521,43 +1563,73 @@ cube_coord(PG_FUNCTION_ARGS)
 }
 
 
-/*
- * This function works like cube_coord(),
- * but rearranges coordinates of corners to get cube representation
- * in the form of (lower left, upper right).
- * For historical reasons that extension allows us to create cubes in form
- * ((2,1),(1,2)) and instead of normalizing such cube to ((1,1),(2,2)) it
- * stores cube in original way. But to get cubes ordered by one of dimensions
- * directly from the index without extra sort step we need some
- * representation-independent coordinate getter. This function implements it.
+/*----
+ * This function works like cube_coord(), but rearranges coordinates in the
+ * way suitable to support coordinate ordering using KNN-GiST.  For historical
+ * reasons this extension allows us to create cubes in form ((2,1),(1,2)) and
+ * instead of normalizing such cube to ((1,1),(2,2)) it stores cube in original
+ * way.  But in order to get cubes ordered by one of dimensions from the index
+ * without explicit sort step we need this representation-independent coordinate
+ * getter.  Moreover, indexed dataset may contain cubes of different dimensions
+ * number.  Accordingly, this coordinate getter should be able to return
+ * lower/upper bound for particular dimension independently on number of cube
+ * dimensions.
+ *
+ * Long story short, this function uses following meaning of coordinates:
+ * # (2 * N - 1) -- lower bound of Nth dimension,
+ * # (2 * N) -- upper bound of Nth dimension.
+ *
+ * When given coordinate exceeds number of cube dimensions, then 0 returned
+ * (reproducing logic of GiST indexing of variable-length cubes).
  */
 Datum
 cube_coord_llur(PG_FUNCTION_ARGS)
 {
 	NDBOX	   *cube = PG_GETARG_NDBOX(0);
 	int			coord = PG_GETARG_INT32(1);
+	bool		inverse = false;
+	float8		result;
 
-	if (coord <= 0 || coord > 2 * DIM(cube))
+	/* 0 is the only unsupported coordinate value */
+	if (coord <= 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
 				 errmsg("cube index %d is out of bounds", coord)));
 
-	if (coord <= DIM(cube))
+	if (coord <= 2 * DIM(cube))
 	{
+		/* dimension index */
+		int		index = (coord - 1) / 2;
+		/* whether this is upper bound (lower bound otherwise) */
+		bool	upper = ((coord - 1) % 2 == 1);
+
 		if (IS_POINT(cube))
-			PG_RETURN_FLOAT8(cube->x[coord - 1]);
+		{
+			result = cube->x[index];
+		}
 		else
-			PG_RETURN_FLOAT8(Min(cube->x[coord - 1],
-								 cube->x[coord - 1 + DIM(cube)]));
+		{
+			if (upper)
+				result = Max(cube->x[index], cube->x[index + DIM(cube)]);
+			else
+				result = Min(cube->x[index], cube->x[index + DIM(cube)]);
+		}
 	}
 	else
 	{
-		if (IS_POINT(cube))
-			PG_RETURN_FLOAT8(cube->x[(coord - 1) % DIM(cube)]);
-		else
-			PG_RETURN_FLOAT8(Max(cube->x[coord - 1],
-								 cube->x[coord - 1 - DIM(cube)]));
+		/*
+		 * Return zero if coordinate is out of bound.  That reproduces logic of
+		 * how cubes with low dimension number are expanded during GiST
+		 * indexing.
+		 */
+		result = 0.0;
 	}
+
+	/* Inverse value if needed */
+	if (inverse)
+		result = -result;
+
+	PG_RETURN_FLOAT8(result);
 }
 
 /* Increase or decrease box size by a radius in at least n dimensions. */
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
index 9ac2a71295..f537dc8c70 100644
--- a/contrib/cube/expected/cube.out
+++ b/contrib/cube/expected/cube.out
@@ -1495,25 +1495,25 @@ SELECT cube(array[40,50,60], array[10,20,30])~>1;
 SELECT cube(array[10,20,30], array[40,50,60])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[10,20,30], array[40,50,60])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>0;
@@ -1521,7 +1521,7 @@ ERROR:  cube index 0 is out of bounds
 SELECT cube(array[40,50,60], array[10,20,30])~>4;
  ?column? 
 ----------
-       40
+       50
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
@@ -1552,25 +1552,28 @@ SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c;
  (2424, 160),(2424, 81)
 (5 rows)
 
--- kNN with index
+-- Test kNN
+INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
+SET enable_seqscan = false;
+-- Test different metrics
 SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            |       dist       
 -------------------------+------------------
  (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
  (759, 187),(662, 163)   |              162
  (948, 1201),(907, 1156) | 772.000647668122
  (1444, 403),(1346, 344) |              846
- (369, 1457),(278, 1409) |              909
 (5 rows)
 
 SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            | dist 
 -------------------------+------
  (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
  (759, 187),(662, 163)   |  162
  (948, 1201),(907, 1156) |  656
  (1444, 403),(1346, 344) |  846
- (369, 1457),(278, 1409) |  909
 (5 rows)
 
 SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
@@ -1578,133 +1581,203 @@ SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c
 -------------------------+------
  (337, 455),(240, 359)   |    0
  (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
  (1444, 403),(1346, 344) |  846
  (369, 1457),(278, 1409) |  909
- (948, 1201),(907, 1156) | 1063
 (5 rows)
 
--- kNN-based sorting
-SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
-             c             
----------------------------
- (54, 38679),(3, 38602)
- (83, 10271),(15, 10265)
- (122, 46832),(64, 46762)
- (167, 17214),(92, 17184)
- (161, 24465),(107, 24374)
- (162, 26040),(120, 25963)
- (154, 4019),(138, 3990)
- (259, 1850),(175, 1820)
- (207, 40886),(179, 40879)
- (288, 49588),(204, 49571)
- (270, 32616),(226, 32607)
- (318, 31489),(235, 31404)
- (337, 455),(240, 359)
- (270, 29508),(264, 29440)
- (369, 1457),(278, 1409)
+-- Test sorting by coordinates
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
-             c             
----------------------------
- (30333, 50),(30273, 6)
- (43301, 75),(43227, 43)
- (19650, 142),(19630, 51)
- (2424, 160),(2424, 81)
- (3449, 171),(3354, 108)
- (18037, 155),(17941, 109)
- (28511, 208),(28479, 114)
- (19946, 217),(19941, 118)
- (16906, 191),(16816, 139)
- (759, 187),(662, 163)
- (22684, 266),(22656, 181)
- (24423, 255),(24360, 213)
- (45989, 249),(45910, 222)
- (11399, 377),(11360, 294)
- (12162, 389),(12103, 309)
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
-               c               
--------------------------------
- (50027, 49230),(49951, 49214)
- (49980, 35004),(49937, 34963)
- (49985, 6436),(49927, 6338)
- (49999, 27218),(49908, 27176)
- (49954, 1340),(49905, 1294)
- (49944, 25163),(49902, 25153)
- (49981, 34876),(49898, 34786)
- (49957, 43390),(49897, 43384)
- (49853, 18504),(49848, 18503)
- (49902, 41752),(49818, 41746)
- (49907, 30225),(49810, 30158)
- (49843, 5175),(49808, 5145)
- (49887, 24274),(49805, 24184)
- (49847, 7128),(49798, 7067)
- (49820, 7990),(49771, 7967)
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
-               c               
--------------------------------
- (36311, 50073),(36258, 49987)
- (30746, 50040),(30727, 49992)
- (2168, 50012),(2108, 49914)
- (21551, 49983),(21492, 49885)
- (17954, 49975),(17865, 49915)
- (3531, 49962),(3463, 49934)
- (19128, 49932),(19112, 49849)
- (31287, 49923),(31236, 49913)
- (43925, 49912),(43888, 49878)
- (29261, 49910),(29247, 49818)
- (14913, 49873),(14849, 49836)
- (20007, 49858),(19921, 49778)
- (38266, 49852),(38233, 49844)
- (37595, 49849),(37581, 49834)
- (46151, 49848),(46058, 49830)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
--- same thing for index with points
-CREATE TABLE test_point(c cube);
-INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
-CREATE INDEX ON test_point USING gist(c);
-SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
-            c             
---------------------------
- (54, 38679, 3, 38602)
- (83, 10271, 15, 10265)
- (122, 46832, 64, 46762)
- (154, 4019, 138, 3990)
- (161, 24465, 107, 24374)
- (162, 26040, 120, 25963)
- (167, 17214, 92, 17184)
- (207, 40886, 179, 40879)
- (259, 1850, 175, 1820)
- (270, 29508, 264, 29440)
- (270, 32616, 226, 32607)
- (288, 49588, 204, 49571)
- (318, 31489, 235, 31404)
- (326, 18837, 285, 18817)
- (337, 455, 240, 359)
+-- Same queries with sequential scan (should give the same results as above)
+RESET enable_seqscan;
+SET enable_indexscan = OFF;
+SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            |       dist       
+-------------------------+------------------
+ (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
+ (759, 187),(662, 163)   |              162
+ (948, 1201),(907, 1156) | 772.000647668122
+ (1444, 403),(1346, 344) |              846
+(5 rows)
+
+SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
+ (759, 187),(662, 163)   |  162
+ (948, 1201),(907, 1156) |  656
+ (1444, 403),(1346, 344) |  846
+(5 rows)
+
+SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
+ (1444, 403),(1346, 344) |  846
+ (369, 1457),(278, 1409) |  909
+(5 rows)
+
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
+(15 rows)
+
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
+(15 rows)
+
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
-              c               
-------------------------------
- (30746, 50040, 30727, 49992)
- (36311, 50073, 36258, 49987)
- (3531, 49962, 3463, 49934)
- (17954, 49975, 17865, 49915)
- (2168, 50012, 2108, 49914)
- (31287, 49923, 31236, 49913)
- (21551, 49983, 21492, 49885)
- (43925, 49912, 43888, 49878)
- (19128, 49932, 19112, 49849)
- (38266, 49852, 38233, 49844)
- (14913, 49873, 14849, 49836)
- (37595, 49849, 37581, 49834)
- (46151, 49848, 46058, 49830)
- (29261, 49910, 29247, 49818)
- (19233, 49824, 19185, 49794)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
+RESET enable_indexscan;
diff --git a/contrib/cube/expected/cube_1.out b/contrib/cube/expected/cube_1.out
index 194c3e693f..db43406fd0 100644
--- a/contrib/cube/expected/cube_1.out
+++ b/contrib/cube/expected/cube_1.out
@@ -1495,25 +1495,25 @@ SELECT cube(array[40,50,60], array[10,20,30])~>1;
 SELECT cube(array[10,20,30], array[40,50,60])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[10,20,30], array[40,50,60])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>0;
@@ -1521,7 +1521,7 @@ ERROR:  cube index 0 is out of bounds
 SELECT cube(array[40,50,60], array[10,20,30])~>4;
  ?column? 
 ----------
-       40
+       50
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
@@ -1552,25 +1552,28 @@ SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c;
  (2424, 160),(2424, 81)
 (5 rows)
 
--- kNN with index
+-- Test kNN
+INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
+SET enable_seqscan = false;
+-- Test different metrics
 SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            |       dist       
 -------------------------+------------------
  (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
  (759, 187),(662, 163)   |              162
  (948, 1201),(907, 1156) | 772.000647668122
  (1444, 403),(1346, 344) |              846
- (369, 1457),(278, 1409) |              909
 (5 rows)
 
 SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            | dist 
 -------------------------+------
  (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
  (759, 187),(662, 163)   |  162
  (948, 1201),(907, 1156) |  656
  (1444, 403),(1346, 344) |  846
- (369, 1457),(278, 1409) |  909
 (5 rows)
 
 SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
@@ -1578,133 +1581,203 @@ SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c
 -------------------------+------
  (337, 455),(240, 359)   |    0
  (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
  (1444, 403),(1346, 344) |  846
  (369, 1457),(278, 1409) |  909
- (948, 1201),(907, 1156) | 1063
 (5 rows)
 
--- kNN-based sorting
-SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
-             c             
----------------------------
- (54, 38679),(3, 38602)
- (83, 10271),(15, 10265)
- (122, 46832),(64, 46762)
- (167, 17214),(92, 17184)
- (161, 24465),(107, 24374)
- (162, 26040),(120, 25963)
- (154, 4019),(138, 3990)
- (259, 1850),(175, 1820)
- (207, 40886),(179, 40879)
- (288, 49588),(204, 49571)
- (270, 32616),(226, 32607)
- (318, 31489),(235, 31404)
- (337, 455),(240, 359)
- (270, 29508),(264, 29440)
- (369, 1457),(278, 1409)
+-- Test sorting by coordinates
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
-             c             
----------------------------
- (30333, 50),(30273, 6)
- (43301, 75),(43227, 43)
- (19650, 142),(19630, 51)
- (2424, 160),(2424, 81)
- (3449, 171),(3354, 108)
- (18037, 155),(17941, 109)
- (28511, 208),(28479, 114)
- (19946, 217),(19941, 118)
- (16906, 191),(16816, 139)
- (759, 187),(662, 163)
- (22684, 266),(22656, 181)
- (24423, 255),(24360, 213)
- (45989, 249),(45910, 222)
- (11399, 377),(11360, 294)
- (12162, 389),(12103, 309)
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
-               c               
--------------------------------
- (50027, 49230),(49951, 49214)
- (49980, 35004),(49937, 34963)
- (49985, 6436),(49927, 6338)
- (49999, 27218),(49908, 27176)
- (49954, 1340),(49905, 1294)
- (49944, 25163),(49902, 25153)
- (49981, 34876),(49898, 34786)
- (49957, 43390),(49897, 43384)
- (49853, 18504),(49848, 18503)
- (49902, 41752),(49818, 41746)
- (49907, 30225),(49810, 30158)
- (49843, 5175),(49808, 5145)
- (49887, 24274),(49805, 24184)
- (49847, 7128),(49798, 7067)
- (49820, 7990),(49771, 7967)
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
-               c               
--------------------------------
- (36311, 50073),(36258, 49987)
- (30746, 50040),(30727, 49992)
- (2168, 50012),(2108, 49914)
- (21551, 49983),(21492, 49885)
- (17954, 49975),(17865, 49915)
- (3531, 49962),(3463, 49934)
- (19128, 49932),(19112, 49849)
- (31287, 49923),(31236, 49913)
- (43925, 49912),(43888, 49878)
- (29261, 49910),(29247, 49818)
- (14913, 49873),(14849, 49836)
- (20007, 49858),(19921, 49778)
- (38266, 49852),(38233, 49844)
- (37595, 49849),(37581, 49834)
- (46151, 49848),(46058, 49830)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
--- same thing for index with points
-CREATE TABLE test_point(c cube);
-INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
-CREATE INDEX ON test_point USING gist(c);
-SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
-            c             
---------------------------
- (54, 38679, 3, 38602)
- (83, 10271, 15, 10265)
- (122, 46832, 64, 46762)
- (154, 4019, 138, 3990)
- (161, 24465, 107, 24374)
- (162, 26040, 120, 25963)
- (167, 17214, 92, 17184)
- (207, 40886, 179, 40879)
- (259, 1850, 175, 1820)
- (270, 29508, 264, 29440)
- (270, 32616, 226, 32607)
- (288, 49588, 204, 49571)
- (318, 31489, 235, 31404)
- (326, 18837, 285, 18817)
- (337, 455, 240, 359)
+-- Same queries with sequential scan (should give the same results as above)
+RESET enable_seqscan;
+SET enable_indexscan = OFF;
+SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            |       dist       
+-------------------------+------------------
+ (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
+ (759, 187),(662, 163)   |              162
+ (948, 1201),(907, 1156) | 772.000647668122
+ (1444, 403),(1346, 344) |              846
+(5 rows)
+
+SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
+ (759, 187),(662, 163)   |  162
+ (948, 1201),(907, 1156) |  656
+ (1444, 403),(1346, 344) |  846
+(5 rows)
+
+SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
+ (1444, 403),(1346, 344) |  846
+ (369, 1457),(278, 1409) |  909
+(5 rows)
+
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
+(15 rows)
+
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
+(15 rows)
+
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
-              c               
-------------------------------
- (30746, 50040, 30727, 49992)
- (36311, 50073, 36258, 49987)
- (3531, 49962, 3463, 49934)
- (17954, 49975, 17865, 49915)
- (2168, 50012, 2108, 49914)
- (31287, 49923, 31236, 49913)
- (21551, 49983, 21492, 49885)
- (43925, 49912, 43888, 49878)
- (19128, 49932, 19112, 49849)
- (38266, 49852, 38233, 49844)
- (14913, 49873, 14849, 49836)
- (37595, 49849, 37581, 49834)
- (46151, 49848, 46058, 49830)
- (29261, 49910, 29247, 49818)
- (19233, 49824, 19185, 49794)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
+RESET enable_indexscan;
diff --git a/contrib/cube/expected/cube_2.out b/contrib/cube/expected/cube_2.out
index 680a46b789..b3ad8b05d9 100644
--- a/contrib/cube/expected/cube_2.out
+++ b/contrib/cube/expected/cube_2.out
@@ -1495,25 +1495,25 @@ SELECT cube(array[40,50,60], array[10,20,30])~>1;
 SELECT cube(array[10,20,30], array[40,50,60])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[10,20,30], array[40,50,60])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>0;
@@ -1521,7 +1521,7 @@ ERROR:  cube index 0 is out of bounds
 SELECT cube(array[40,50,60], array[10,20,30])~>4;
  ?column? 
 ----------
-       40
+       50
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
@@ -1552,25 +1552,28 @@ SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c;
  (2424, 160),(2424, 81)
 (5 rows)
 
--- kNN with index
+-- Test kNN
+INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
+SET enable_seqscan = false;
+-- Test different metrics
 SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            |       dist       
 -------------------------+------------------
  (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
  (759, 187),(662, 163)   |              162
  (948, 1201),(907, 1156) | 772.000647668122
  (1444, 403),(1346, 344) |              846
- (369, 1457),(278, 1409) |              909
 (5 rows)
 
 SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            | dist 
 -------------------------+------
  (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
  (759, 187),(662, 163)   |  162
  (948, 1201),(907, 1156) |  656
  (1444, 403),(1346, 344) |  846
- (369, 1457),(278, 1409) |  909
 (5 rows)
 
 SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
@@ -1578,133 +1581,203 @@ SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c
 -------------------------+------
  (337, 455),(240, 359)   |    0
  (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
  (1444, 403),(1346, 344) |  846
  (369, 1457),(278, 1409) |  909
- (948, 1201),(907, 1156) | 1063
 (5 rows)
 
--- kNN-based sorting
-SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
-             c             
----------------------------
- (54, 38679),(3, 38602)
- (83, 10271),(15, 10265)
- (122, 46832),(64, 46762)
- (167, 17214),(92, 17184)
- (161, 24465),(107, 24374)
- (162, 26040),(120, 25963)
- (154, 4019),(138, 3990)
- (259, 1850),(175, 1820)
- (207, 40886),(179, 40879)
- (288, 49588),(204, 49571)
- (270, 32616),(226, 32607)
- (318, 31489),(235, 31404)
- (337, 455),(240, 359)
- (270, 29508),(264, 29440)
- (369, 1457),(278, 1409)
+-- Test sorting by coordinates
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
-             c             
----------------------------
- (30333, 50),(30273, 6)
- (43301, 75),(43227, 43)
- (19650, 142),(19630, 51)
- (2424, 160),(2424, 81)
- (3449, 171),(3354, 108)
- (18037, 155),(17941, 109)
- (28511, 208),(28479, 114)
- (19946, 217),(19941, 118)
- (16906, 191),(16816, 139)
- (759, 187),(662, 163)
- (22684, 266),(22656, 181)
- (24423, 255),(24360, 213)
- (45989, 249),(45910, 222)
- (11399, 377),(11360, 294)
- (12162, 389),(12103, 309)
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
-               c               
--------------------------------
- (50027, 49230),(49951, 49214)
- (49980, 35004),(49937, 34963)
- (49985, 6436),(49927, 6338)
- (49999, 27218),(49908, 27176)
- (49954, 1340),(49905, 1294)
- (49944, 25163),(49902, 25153)
- (49981, 34876),(49898, 34786)
- (49957, 43390),(49897, 43384)
- (49853, 18504),(49848, 18503)
- (49902, 41752),(49818, 41746)
- (49907, 30225),(49810, 30158)
- (49843, 5175),(49808, 5145)
- (49887, 24274),(49805, 24184)
- (49847, 7128),(49798, 7067)
- (49820, 7990),(49771, 7967)
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
-               c               
--------------------------------
- (36311, 50073),(36258, 49987)
- (30746, 50040),(30727, 49992)
- (2168, 50012),(2108, 49914)
- (21551, 49983),(21492, 49885)
- (17954, 49975),(17865, 49915)
- (3531, 49962),(3463, 49934)
- (19128, 49932),(19112, 49849)
- (31287, 49923),(31236, 49913)
- (43925, 49912),(43888, 49878)
- (29261, 49910),(29247, 49818)
- (14913, 49873),(14849, 49836)
- (20007, 49858),(19921, 49778)
- (38266, 49852),(38233, 49844)
- (37595, 49849),(37581, 49834)
- (46151, 49848),(46058, 49830)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
--- same thing for index with points
-CREATE TABLE test_point(c cube);
-INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
-CREATE INDEX ON test_point USING gist(c);
-SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
-            c             
---------------------------
- (54, 38679, 3, 38602)
- (83, 10271, 15, 10265)
- (122, 46832, 64, 46762)
- (154, 4019, 138, 3990)
- (161, 24465, 107, 24374)
- (162, 26040, 120, 25963)
- (167, 17214, 92, 17184)
- (207, 40886, 179, 40879)
- (259, 1850, 175, 1820)
- (270, 29508, 264, 29440)
- (270, 32616, 226, 32607)
- (288, 49588, 204, 49571)
- (318, 31489, 235, 31404)
- (326, 18837, 285, 18817)
- (337, 455, 240, 359)
+-- Same queries with sequential scan (should give the same results as above)
+RESET enable_seqscan;
+SET enable_indexscan = OFF;
+SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            |       dist       
+-------------------------+------------------
+ (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
+ (759, 187),(662, 163)   |              162
+ (948, 1201),(907, 1156) | 772.000647668122
+ (1444, 403),(1346, 344) |              846
+(5 rows)
+
+SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
+ (759, 187),(662, 163)   |  162
+ (948, 1201),(907, 1156) |  656
+ (1444, 403),(1346, 344) |  846
+(5 rows)
+
+SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
+ (1444, 403),(1346, 344) |  846
+ (369, 1457),(278, 1409) |  909
+(5 rows)
+
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
+(15 rows)
+
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
+(15 rows)
+
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
-              c               
-------------------------------
- (30746, 50040, 30727, 49992)
- (36311, 50073, 36258, 49987)
- (3531, 49962, 3463, 49934)
- (17954, 49975, 17865, 49915)
- (2168, 50012, 2108, 49914)
- (31287, 49923, 31236, 49913)
- (21551, 49983, 21492, 49885)
- (43925, 49912, 43888, 49878)
- (19128, 49932, 19112, 49849)
- (38266, 49852, 38233, 49844)
- (14913, 49873, 14849, 49836)
- (37595, 49849, 37581, 49834)
- (46151, 49848, 46058, 49830)
- (29261, 49910, 29247, 49818)
- (19233, 49824, 19185, 49794)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
+RESET enable_indexscan;
diff --git a/contrib/cube/expected/cube_3.out b/contrib/cube/expected/cube_3.out
index 8ad92221ad..4a6c5f613e 100644
--- a/contrib/cube/expected/cube_3.out
+++ b/contrib/cube/expected/cube_3.out
@@ -1495,25 +1495,25 @@ SELECT cube(array[40,50,60], array[10,20,30])~>1;
 SELECT cube(array[10,20,30], array[40,50,60])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[10,20,30], array[40,50,60])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>0;
@@ -1521,7 +1521,7 @@ ERROR:  cube index 0 is out of bounds
 SELECT cube(array[40,50,60], array[10,20,30])~>4;
  ?column? 
 ----------
-       40
+       50
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
@@ -1552,25 +1552,28 @@ SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c;
  (2424, 160),(2424, 81)
 (5 rows)
 
--- kNN with index
+-- Test kNN
+INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
+SET enable_seqscan = false;
+-- Test different metrics
 SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            |       dist       
 -------------------------+------------------
  (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
  (759, 187),(662, 163)   |              162
  (948, 1201),(907, 1156) | 772.000647668122
  (1444, 403),(1346, 344) |              846
- (369, 1457),(278, 1409) |              909
 (5 rows)
 
 SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            | dist 
 -------------------------+------
  (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
  (759, 187),(662, 163)   |  162
  (948, 1201),(907, 1156) |  656
  (1444, 403),(1346, 344) |  846
- (369, 1457),(278, 1409) |  909
 (5 rows)
 
 SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
@@ -1578,133 +1581,203 @@ SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c
 -------------------------+------
  (337, 455),(240, 359)   |    0
  (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
  (1444, 403),(1346, 344) |  846
  (369, 1457),(278, 1409) |  909
- (948, 1201),(907, 1156) | 1063
 (5 rows)
 
--- kNN-based sorting
-SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
-             c             
----------------------------
- (54, 38679),(3, 38602)
- (83, 10271),(15, 10265)
- (122, 46832),(64, 46762)
- (167, 17214),(92, 17184)
- (161, 24465),(107, 24374)
- (162, 26040),(120, 25963)
- (154, 4019),(138, 3990)
- (259, 1850),(175, 1820)
- (207, 40886),(179, 40879)
- (288, 49588),(204, 49571)
- (270, 32616),(226, 32607)
- (318, 31489),(235, 31404)
- (337, 455),(240, 359)
- (270, 29508),(264, 29440)
- (369, 1457),(278, 1409)
+-- Test sorting by coordinates
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
-             c             
----------------------------
- (30333, 50),(30273, 6)
- (43301, 75),(43227, 43)
- (19650, 142),(19630, 51)
- (2424, 160),(2424, 81)
- (3449, 171),(3354, 108)
- (18037, 155),(17941, 109)
- (28511, 208),(28479, 114)
- (19946, 217),(19941, 118)
- (16906, 191),(16816, 139)
- (759, 187),(662, 163)
- (22684, 266),(22656, 181)
- (24423, 255),(24360, 213)
- (45989, 249),(45910, 222)
- (11399, 377),(11360, 294)
- (12162, 389),(12103, 309)
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
-               c               
--------------------------------
- (50027, 49230),(49951, 49214)
- (49980, 35004),(49937, 34963)
- (49985, 6436),(49927, 6338)
- (49999, 27218),(49908, 27176)
- (49954, 1340),(49905, 1294)
- (49944, 25163),(49902, 25153)
- (49981, 34876),(49898, 34786)
- (49957, 43390),(49897, 43384)
- (49853, 18504),(49848, 18503)
- (49902, 41752),(49818, 41746)
- (49907, 30225),(49810, 30158)
- (49843, 5175),(49808, 5145)
- (49887, 24274),(49805, 24184)
- (49847, 7128),(49798, 7067)
- (49820, 7990),(49771, 7967)
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
-               c               
--------------------------------
- (36311, 50073),(36258, 49987)
- (30746, 50040),(30727, 49992)
- (2168, 50012),(2108, 49914)
- (21551, 49983),(21492, 49885)
- (17954, 49975),(17865, 49915)
- (3531, 49962),(3463, 49934)
- (19128, 49932),(19112, 49849)
- (31287, 49923),(31236, 49913)
- (43925, 49912),(43888, 49878)
- (29261, 49910),(29247, 49818)
- (14913, 49873),(14849, 49836)
- (20007, 49858),(19921, 49778)
- (38266, 49852),(38233, 49844)
- (37595, 49849),(37581, 49834)
- (46151, 49848),(46058, 49830)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
--- same thing for index with points
-CREATE TABLE test_point(c cube);
-INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
-CREATE INDEX ON test_point USING gist(c);
-SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
-            c             
---------------------------
- (54, 38679, 3, 38602)
- (83, 10271, 15, 10265)
- (122, 46832, 64, 46762)
- (154, 4019, 138, 3990)
- (161, 24465, 107, 24374)
- (162, 26040, 120, 25963)
- (167, 17214, 92, 17184)
- (207, 40886, 179, 40879)
- (259, 1850, 175, 1820)
- (270, 29508, 264, 29440)
- (270, 32616, 226, 32607)
- (288, 49588, 204, 49571)
- (318, 31489, 235, 31404)
- (326, 18837, 285, 18817)
- (337, 455, 240, 359)
+-- Same queries with sequential scan (should give the same results as above)
+RESET enable_seqscan;
+SET enable_indexscan = OFF;
+SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            |       dist       
+-------------------------+------------------
+ (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
+ (759, 187),(662, 163)   |              162
+ (948, 1201),(907, 1156) | 772.000647668122
+ (1444, 403),(1346, 344) |              846
+(5 rows)
+
+SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
+ (759, 187),(662, 163)   |  162
+ (948, 1201),(907, 1156) |  656
+ (1444, 403),(1346, 344) |  846
+(5 rows)
+
+SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
+ (1444, 403),(1346, 344) |  846
+ (369, 1457),(278, 1409) |  909
+(5 rows)
+
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
+(15 rows)
+
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
+(15 rows)
+
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
-              c               
-------------------------------
- (30746, 50040, 30727, 49992)
- (36311, 50073, 36258, 49987)
- (3531, 49962, 3463, 49934)
- (17954, 49975, 17865, 49915)
- (2168, 50012, 2108, 49914)
- (31287, 49923, 31236, 49913)
- (21551, 49983, 21492, 49885)
- (43925, 49912, 43888, 49878)
- (19128, 49932, 19112, 49849)
- (38266, 49852, 38233, 49844)
- (14913, 49873, 14849, 49836)
- (37595, 49849, 37581, 49834)
- (46151, 49848, 46058, 49830)
- (29261, 49910, 29247, 49818)
- (19233, 49824, 19185, 49794)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
+RESET enable_indexscan;
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
index df5e1b286b..46b93375b6 100644
--- a/contrib/cube/sql/cube.sql
+++ b/contrib/cube/sql/cube.sql
@@ -372,20 +372,29 @@ SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' ORDER BY c;
 -- Test sorting
 SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c;
 
--- kNN with index
+-- Test kNN
+INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
+SET enable_seqscan = false;
+
+-- Test different metrics
 SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
 SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
 SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
 
--- kNN-based sorting
-SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
-SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
-SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
-SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
-
--- same thing for index with points
-CREATE TABLE test_point(c cube);
-INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
-CREATE INDEX ON test_point USING gist(c);
-SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
-SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
+-- Test sorting by coordinates
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+
+-- Same queries with sequential scan (should give the same results as above)
+RESET enable_seqscan;
+SET enable_indexscan = OFF;
+SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
+SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
+SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+RESET enable_indexscan;
diff --git a/doc/src/sgml/cube.sgml b/doc/src/sgml/cube.sgml
index 1ffc40f1a5..3d503719c5 100644
--- a/doc/src/sgml/cube.sgml
+++ b/doc/src/sgml/cube.sgml
@@ -186,10 +186,11 @@
       <entry><literal>a ~&gt; n</></entry>
       <entry><type>float8</></entry>
       <entry>
-        Get <replaceable>n</>-th coordinate in <quote>normalized</> cube
-        representation, in which the coordinates have been rearranged into
-        the form <quote>lower left &mdash; upper right</>; that is, the
-        smaller endpoint along each dimension appears first.
+        Get <replaceable>n</>-th coordinate of cube in following way:
+        n = 2 * k - 1 means lower bound of <replaceable>k</>-th
+        dimension, n = 2 * k means upper bound of
+        <replaceable>k</>-th dimension.  This operator is designed
+        for KNN-GiST support.
       </entry>
      </row>
 
0001-cube-knn-fix-pg10.patchapplication/octet-stream; name=0001-cube-knn-fix-pg10.patchDownload
diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
index 149558c763..e695a5e16c 100644
--- a/contrib/cube/cube.c
+++ b/contrib/cube/cube.c
@@ -1336,15 +1336,55 @@ g_cube_distance(PG_FUNCTION_ARGS)
 
 	if (strategy == CubeKNNDistanceCoord)
 	{
+		/*
+		 * Handle ordering by ~> operator.  See comments of cube_coord_llur()
+		 * for details
+		 */
 		int			coord = PG_GETARG_INT32(1);
+		bool		isLeaf = GistPageIsLeaf(entry->page);
 
-		if (DIM(cube) == 0)
-			retval = 0.0;
-		else if (IS_POINT(cube))
-			retval = cube->x[(coord - 1) % DIM(cube)];
+		/* 0 is the only unsupported coordinate value */
+		if (coord <= 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
+					 errmsg("cube index %d is out of bounds", coord)));
+
+		if (coord <= 2 * DIM(cube))
+		{
+			/* dimension index */
+			int		index = (coord - 1) / 2;
+			/* whether this is upper bound (lower bound otherwise) */
+			bool	upper = ((coord - 1) % 2 == 1);
+
+			if (IS_POINT(cube))
+			{
+				retval = cube->x[index];
+			}
+			else
+			{
+				if (isLeaf)
+				{
+					/* For leaf just return required upper/lower bound */
+					if (upper)
+						retval = Max(cube->x[index], cube->x[index + DIM(cube)]);
+					else
+						retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
+				}
+				else
+				{
+					/*
+					 * For non-leaf we should always return lower bound,
+					 * because even upper bound of a child in the subtree can
+					 * be as small as our lower bound.
+					 */
+					retval = Min(cube->x[index], cube->x[index + DIM(cube)]);
+				}
+			}
+		}
 		else
-			retval = Min(cube->x[(coord - 1) % DIM(cube)],
-						 cube->x[(coord - 1) % DIM(cube) + DIM(cube)]);
+		{
+			retval = 0.0;
+		}
 	}
 	else
 	{
@@ -1491,43 +1531,73 @@ cube_coord(PG_FUNCTION_ARGS)
 }
 
 
-/*
- * This function works like cube_coord(),
- * but rearranges coordinates of corners to get cube representation
- * in the form of (lower left, upper right).
- * For historical reasons that extension allows us to create cubes in form
- * ((2,1),(1,2)) and instead of normalizing such cube to ((1,1),(2,2)) it
- * stores cube in original way. But to get cubes ordered by one of dimensions
- * directly from the index without extra sort step we need some
- * representation-independent coordinate getter. This function implements it.
+/*----
+ * This function works like cube_coord(), but rearranges coordinates in the
+ * way suitable to support coordinate ordering using KNN-GiST.  For historical
+ * reasons this extension allows us to create cubes in form ((2,1),(1,2)) and
+ * instead of normalizing such cube to ((1,1),(2,2)) it stores cube in original
+ * way.  But in order to get cubes ordered by one of dimensions from the index
+ * without explicit sort step we need this representation-independent coordinate
+ * getter.  Moreover, indexed dataset may contain cubes of different dimensions
+ * number.  Accordingly, this coordinate getter should be able to return
+ * lower/upper bound for particular dimension independently on number of cube
+ * dimensions.
+ *
+ * Long story short, this function uses following meaning of coordinates:
+ * # (2 * N - 1) -- lower bound of Nth dimension,
+ * # (2 * N) -- upper bound of Nth dimension.
+ *
+ * When given coordinate exceeds number of cube dimensions, then 0 returned
+ * (reproducing logic of GiST indexing of variable-length cubes).
  */
 Datum
 cube_coord_llur(PG_FUNCTION_ARGS)
 {
 	NDBOX	   *cube = PG_GETARG_NDBOX(0);
 	int			coord = PG_GETARG_INT32(1);
+	bool		inverse = false;
+	float8		result;
 
-	if (coord <= 0 || coord > 2 * DIM(cube))
+	/* 0 is the only unsupported coordinate value */
+	if (coord <= 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
 				 errmsg("cube index %d is out of bounds", coord)));
 
-	if (coord <= DIM(cube))
+	if (coord <= 2 * DIM(cube))
 	{
+		/* dimension index */
+		int		index = (coord - 1) / 2;
+		/* whether this is upper bound (lower bound otherwise) */
+		bool	upper = ((coord - 1) % 2 == 1);
+
 		if (IS_POINT(cube))
-			PG_RETURN_FLOAT8(cube->x[coord - 1]);
+		{
+			result = cube->x[index];
+		}
 		else
-			PG_RETURN_FLOAT8(Min(cube->x[coord - 1],
-								 cube->x[coord - 1 + DIM(cube)]));
+		{
+			if (upper)
+				result = Max(cube->x[index], cube->x[index + DIM(cube)]);
+			else
+				result = Min(cube->x[index], cube->x[index + DIM(cube)]);
+		}
 	}
 	else
 	{
-		if (IS_POINT(cube))
-			PG_RETURN_FLOAT8(cube->x[(coord - 1) % DIM(cube)]);
-		else
-			PG_RETURN_FLOAT8(Max(cube->x[coord - 1],
-								 cube->x[coord - 1 - DIM(cube)]));
+		/*
+		 * Return zero if coordinate is out of bound.  That reproduces logic of
+		 * how cubes with low dimension number are expanded during GiST
+		 * indexing.
+		 */
+		result = 0.0;
 	}
+
+	/* Inverse value if needed */
+	if (inverse)
+		result = -result;
+
+	PG_RETURN_FLOAT8(result);
 }
 
 /* Increase or decrease box size by a radius in at least n dimensions. */
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
index 328b3b5f5d..38aee7c65f 100644
--- a/contrib/cube/expected/cube.out
+++ b/contrib/cube/expected/cube.out
@@ -1532,25 +1532,25 @@ SELECT cube(array[40,50,60], array[10,20,30])~>1;
 SELECT cube(array[10,20,30], array[40,50,60])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[10,20,30], array[40,50,60])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>0;
@@ -1558,7 +1558,7 @@ ERROR:  cube index 0 is out of bounds
 SELECT cube(array[40,50,60], array[10,20,30])~>4;
  ?column? 
 ----------
-       40
+       50
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
@@ -1589,25 +1589,28 @@ SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c;
  (2424, 160),(2424, 81)
 (5 rows)
 
--- kNN with index
+-- Test kNN
+INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
+SET enable_seqscan = false;
+-- Test different metrics
 SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            |       dist       
 -------------------------+------------------
  (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
  (759, 187),(662, 163)   |              162
  (948, 1201),(907, 1156) | 772.000647668122
  (1444, 403),(1346, 344) |              846
- (369, 1457),(278, 1409) |              909
 (5 rows)
 
 SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            | dist 
 -------------------------+------
  (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
  (759, 187),(662, 163)   |  162
  (948, 1201),(907, 1156) |  656
  (1444, 403),(1346, 344) |  846
- (369, 1457),(278, 1409) |  909
 (5 rows)
 
 SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
@@ -1615,133 +1618,203 @@ SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c
 -------------------------+------
  (337, 455),(240, 359)   |    0
  (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
  (1444, 403),(1346, 344) |  846
  (369, 1457),(278, 1409) |  909
- (948, 1201),(907, 1156) | 1063
 (5 rows)
 
--- kNN-based sorting
-SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
-             c             
----------------------------
- (54, 38679),(3, 38602)
- (83, 10271),(15, 10265)
- (122, 46832),(64, 46762)
- (167, 17214),(92, 17184)
- (161, 24465),(107, 24374)
- (162, 26040),(120, 25963)
- (154, 4019),(138, 3990)
- (259, 1850),(175, 1820)
- (207, 40886),(179, 40879)
- (288, 49588),(204, 49571)
- (270, 32616),(226, 32607)
- (318, 31489),(235, 31404)
- (337, 455),(240, 359)
- (270, 29508),(264, 29440)
- (369, 1457),(278, 1409)
+-- Test sorting by coordinates
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
-             c             
----------------------------
- (30333, 50),(30273, 6)
- (43301, 75),(43227, 43)
- (19650, 142),(19630, 51)
- (2424, 160),(2424, 81)
- (3449, 171),(3354, 108)
- (18037, 155),(17941, 109)
- (28511, 208),(28479, 114)
- (19946, 217),(19941, 118)
- (16906, 191),(16816, 139)
- (759, 187),(662, 163)
- (22684, 266),(22656, 181)
- (24423, 255),(24360, 213)
- (45989, 249),(45910, 222)
- (11399, 377),(11360, 294)
- (12162, 389),(12103, 309)
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
-               c               
--------------------------------
- (50027, 49230),(49951, 49214)
- (49980, 35004),(49937, 34963)
- (49985, 6436),(49927, 6338)
- (49999, 27218),(49908, 27176)
- (49954, 1340),(49905, 1294)
- (49944, 25163),(49902, 25153)
- (49981, 34876),(49898, 34786)
- (49957, 43390),(49897, 43384)
- (49853, 18504),(49848, 18503)
- (49902, 41752),(49818, 41746)
- (49907, 30225),(49810, 30158)
- (49843, 5175),(49808, 5145)
- (49887, 24274),(49805, 24184)
- (49847, 7128),(49798, 7067)
- (49820, 7990),(49771, 7967)
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
-               c               
--------------------------------
- (36311, 50073),(36258, 49987)
- (30746, 50040),(30727, 49992)
- (2168, 50012),(2108, 49914)
- (21551, 49983),(21492, 49885)
- (17954, 49975),(17865, 49915)
- (3531, 49962),(3463, 49934)
- (19128, 49932),(19112, 49849)
- (31287, 49923),(31236, 49913)
- (43925, 49912),(43888, 49878)
- (29261, 49910),(29247, 49818)
- (14913, 49873),(14849, 49836)
- (20007, 49858),(19921, 49778)
- (38266, 49852),(38233, 49844)
- (37595, 49849),(37581, 49834)
- (46151, 49848),(46058, 49830)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
--- same thing for index with points
-CREATE TABLE test_point(c cube);
-INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
-CREATE INDEX ON test_point USING gist(c);
-SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
-            c             
---------------------------
- (54, 38679, 3, 38602)
- (83, 10271, 15, 10265)
- (122, 46832, 64, 46762)
- (154, 4019, 138, 3990)
- (161, 24465, 107, 24374)
- (162, 26040, 120, 25963)
- (167, 17214, 92, 17184)
- (207, 40886, 179, 40879)
- (259, 1850, 175, 1820)
- (270, 29508, 264, 29440)
- (270, 32616, 226, 32607)
- (288, 49588, 204, 49571)
- (318, 31489, 235, 31404)
- (326, 18837, 285, 18817)
- (337, 455, 240, 359)
+-- Same queries with sequential scan (should give the same results as above)
+RESET enable_seqscan;
+SET enable_indexscan = OFF;
+SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            |       dist       
+-------------------------+------------------
+ (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
+ (759, 187),(662, 163)   |              162
+ (948, 1201),(907, 1156) | 772.000647668122
+ (1444, 403),(1346, 344) |              846
+(5 rows)
+
+SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
+ (759, 187),(662, 163)   |  162
+ (948, 1201),(907, 1156) |  656
+ (1444, 403),(1346, 344) |  846
+(5 rows)
+
+SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
+ (1444, 403),(1346, 344) |  846
+ (369, 1457),(278, 1409) |  909
+(5 rows)
+
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
+(15 rows)
+
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
+(15 rows)
+
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
-              c               
-------------------------------
- (30746, 50040, 30727, 49992)
- (36311, 50073, 36258, 49987)
- (3531, 49962, 3463, 49934)
- (17954, 49975, 17865, 49915)
- (2168, 50012, 2108, 49914)
- (31287, 49923, 31236, 49913)
- (21551, 49983, 21492, 49885)
- (43925, 49912, 43888, 49878)
- (19128, 49932, 19112, 49849)
- (38266, 49852, 38233, 49844)
- (14913, 49873, 14849, 49836)
- (37595, 49849, 37581, 49834)
- (46151, 49848, 46058, 49830)
- (29261, 49910, 29247, 49818)
- (19233, 49824, 19185, 49794)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
+RESET enable_indexscan;
diff --git a/contrib/cube/expected/cube_2.out b/contrib/cube/expected/cube_2.out
index 1aa5cf2f98..530828071f 100644
--- a/contrib/cube/expected/cube_2.out
+++ b/contrib/cube/expected/cube_2.out
@@ -1532,25 +1532,25 @@ SELECT cube(array[40,50,60], array[10,20,30])~>1;
 SELECT cube(array[10,20,30], array[40,50,60])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>2;
  ?column? 
 ----------
-       20
+       40
 (1 row)
 
 SELECT cube(array[10,20,30], array[40,50,60])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>3;
  ?column? 
 ----------
-       30
+       20
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>0;
@@ -1558,7 +1558,7 @@ ERROR:  cube index 0 is out of bounds
 SELECT cube(array[40,50,60], array[10,20,30])~>4;
  ?column? 
 ----------
-       40
+       50
 (1 row)
 
 SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
@@ -1589,25 +1589,28 @@ SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c;
  (2424, 160),(2424, 81)
 (5 rows)
 
--- kNN with index
+-- Test kNN
+INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
+SET enable_seqscan = false;
+-- Test different metrics
 SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            |       dist       
 -------------------------+------------------
  (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
  (759, 187),(662, 163)   |              162
  (948, 1201),(907, 1156) | 772.000647668122
  (1444, 403),(1346, 344) |              846
- (369, 1457),(278, 1409) |              909
 (5 rows)
 
 SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
             c            | dist 
 -------------------------+------
  (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
  (759, 187),(662, 163)   |  162
  (948, 1201),(907, 1156) |  656
  (1444, 403),(1346, 344) |  846
- (369, 1457),(278, 1409) |  909
 (5 rows)
 
 SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
@@ -1615,133 +1618,203 @@ SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c
 -------------------------+------
  (337, 455),(240, 359)   |    0
  (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
  (1444, 403),(1346, 344) |  846
  (369, 1457),(278, 1409) |  909
- (948, 1201),(907, 1156) | 1063
 (5 rows)
 
--- kNN-based sorting
-SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
-             c             
----------------------------
- (54, 38679),(3, 38602)
- (83, 10271),(15, 10265)
- (122, 46832),(64, 46762)
- (167, 17214),(92, 17184)
- (161, 24465),(107, 24374)
- (162, 26040),(120, 25963)
- (154, 4019),(138, 3990)
- (259, 1850),(175, 1820)
- (207, 40886),(179, 40879)
- (288, 49588),(204, 49571)
- (270, 32616),(226, 32607)
- (318, 31489),(235, 31404)
- (337, 455),(240, 359)
- (270, 29508),(264, 29440)
- (369, 1457),(278, 1409)
+-- Test sorting by coordinates
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
-             c             
----------------------------
- (30333, 50),(30273, 6)
- (43301, 75),(43227, 43)
- (19650, 142),(19630, 51)
- (2424, 160),(2424, 81)
- (3449, 171),(3354, 108)
- (18037, 155),(17941, 109)
- (28511, 208),(28479, 114)
- (19946, 217),(19941, 118)
- (16906, 191),(16816, 139)
- (759, 187),(662, 163)
- (22684, 266),(22656, 181)
- (24423, 255),(24360, 213)
- (45989, 249),(45910, 222)
- (11399, 377),(11360, 294)
- (12162, 389),(12103, 309)
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
-               c               
--------------------------------
- (50027, 49230),(49951, 49214)
- (49980, 35004),(49937, 34963)
- (49985, 6436),(49927, 6338)
- (49999, 27218),(49908, 27176)
- (49954, 1340),(49905, 1294)
- (49944, 25163),(49902, 25153)
- (49981, 34876),(49898, 34786)
- (49957, 43390),(49897, 43384)
- (49853, 18504),(49848, 18503)
- (49902, 41752),(49818, 41746)
- (49907, 30225),(49810, 30158)
- (49843, 5175),(49808, 5145)
- (49887, 24274),(49805, 24184)
- (49847, 7128),(49798, 7067)
- (49820, 7990),(49771, 7967)
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
-               c               
--------------------------------
- (36311, 50073),(36258, 49987)
- (30746, 50040),(30727, 49992)
- (2168, 50012),(2108, 49914)
- (21551, 49983),(21492, 49885)
- (17954, 49975),(17865, 49915)
- (3531, 49962),(3463, 49934)
- (19128, 49932),(19112, 49849)
- (31287, 49923),(31236, 49913)
- (43925, 49912),(43888, 49878)
- (29261, 49910),(29247, 49818)
- (14913, 49873),(14849, 49836)
- (20007, 49858),(19921, 49778)
- (38266, 49852),(38233, 49844)
- (37595, 49849),(37581, 49834)
- (46151, 49848),(46058, 49830)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
--- same thing for index with points
-CREATE TABLE test_point(c cube);
-INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
-CREATE INDEX ON test_point USING gist(c);
-SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
-            c             
---------------------------
- (54, 38679, 3, 38602)
- (83, 10271, 15, 10265)
- (122, 46832, 64, 46762)
- (154, 4019, 138, 3990)
- (161, 24465, 107, 24374)
- (162, 26040, 120, 25963)
- (167, 17214, 92, 17184)
- (207, 40886, 179, 40879)
- (259, 1850, 175, 1820)
- (270, 29508, 264, 29440)
- (270, 32616, 226, 32607)
- (288, 49588, 204, 49571)
- (318, 31489, 235, 31404)
- (326, 18837, 285, 18817)
- (337, 455, 240, 359)
+-- Same queries with sequential scan (should give the same results as above)
+RESET enable_seqscan;
+SET enable_indexscan = OFF;
+SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            |       dist       
+-------------------------+------------------
+ (337, 455),(240, 359)   |                0
+ (1, 1)                  | 140.007142674936
+ (759, 187),(662, 163)   |              162
+ (948, 1201),(907, 1156) | 772.000647668122
+ (1444, 403),(1346, 344) |              846
+(5 rows)
+
+SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (1, 1)                  |   99
+ (759, 187),(662, 163)   |  162
+ (948, 1201),(907, 1156) |  656
+ (1444, 403),(1346, 344) |  846
+(5 rows)
+
+SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
+            c            | dist 
+-------------------------+------
+ (337, 455),(240, 359)   |    0
+ (759, 187),(662, 163)   |  162
+ (1, 1)                  |  198
+ (1444, 403),(1346, 344) |  846
+ (369, 1457),(278, 1409) |  909
+(5 rows)
+
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+        3 | (54, 38679),(3, 38602)
+       15 | (83, 10271),(15, 10265)
+       64 | (122, 46832),(64, 46762)
+       92 | (167, 17214),(92, 17184)
+      107 | (161, 24465),(107, 24374)
+      120 | (162, 26040),(120, 25963)
+      138 | (154, 4019),(138, 3990)
+      175 | (259, 1850),(175, 1820)
+      179 | (207, 40886),(179, 40879)
+      204 | (288, 49588),(204, 49571)
+      226 | (270, 32616),(226, 32607)
+      235 | (318, 31489),(235, 31404)
+      240 | (337, 455),(240, 359)
+(15 rows)
+
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (0, 100000)
+        1 | (1, 1)
+       54 | (54, 38679),(3, 38602)
+       83 | (83, 10271),(15, 10265)
+      122 | (122, 46832),(64, 46762)
+      154 | (154, 4019),(138, 3990)
+      161 | (161, 24465),(107, 24374)
+      162 | (162, 26040),(120, 25963)
+      167 | (167, 17214),(92, 17184)
+      207 | (207, 40886),(179, 40879)
+      259 | (259, 1850),(175, 1820)
+      270 | (270, 29508),(264, 29440)
+      270 | (270, 32616),(226, 32607)
+      288 | (288, 49588),(204, 49571)
+      318 | (318, 31489),(235, 31404)
+(15 rows)
+
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+        6 | (30333, 50),(30273, 6)
+       43 | (43301, 75),(43227, 43)
+       51 | (19650, 142),(19630, 51)
+       81 | (2424, 160),(2424, 81)
+      108 | (3449, 171),(3354, 108)
+      109 | (18037, 155),(17941, 109)
+      114 | (28511, 208),(28479, 114)
+      118 | (19946, 217),(19941, 118)
+      139 | (16906, 191),(16816, 139)
+      163 | (759, 187),(662, 163)
+      181 | (22684, 266),(22656, 181)
+      213 | (24423, 255),(24360, 213)
+      222 | (45989, 249),(45910, 222)
 (15 rows)
 
-SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
-              c               
-------------------------------
- (30746, 50040, 30727, 49992)
- (36311, 50073, 36258, 49987)
- (3531, 49962, 3463, 49934)
- (17954, 49975, 17865, 49915)
- (2168, 50012, 2108, 49914)
- (31287, 49923, 31236, 49913)
- (21551, 49983, 21492, 49885)
- (43925, 49912, 43888, 49878)
- (19128, 49932, 19112, 49849)
- (38266, 49852, 38233, 49844)
- (14913, 49873, 14849, 49836)
- (37595, 49849, 37581, 49834)
- (46151, 49848, 46058, 49830)
- (29261, 49910, 29247, 49818)
- (19233, 49824, 19185, 49794)
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+ ?column? |             c             
+----------+---------------------------
+        0 | (100000)
+        1 | (1, 1)
+       50 | (30333, 50),(30273, 6)
+       75 | (43301, 75),(43227, 43)
+      142 | (19650, 142),(19630, 51)
+      155 | (18037, 155),(17941, 109)
+      160 | (2424, 160),(2424, 81)
+      171 | (3449, 171),(3354, 108)
+      187 | (759, 187),(662, 163)
+      191 | (16906, 191),(16816, 139)
+      208 | (28511, 208),(28479, 114)
+      217 | (19946, 217),(19941, 118)
+      249 | (45989, 249),(45910, 222)
+      255 | (24423, 255),(24360, 213)
+      266 | (22684, 266),(22656, 181)
 (15 rows)
 
+RESET enable_indexscan;
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
index 58ea3ad811..ce6f709c83 100644
--- a/contrib/cube/sql/cube.sql
+++ b/contrib/cube/sql/cube.sql
@@ -382,20 +382,29 @@ SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' ORDER BY c;
 -- Test sorting
 SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c;
 
--- kNN with index
+-- Test kNN
+INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases
+SET enable_seqscan = false;
+
+-- Test different metrics
 SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
 SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
 SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
 
--- kNN-based sorting
-SELECT * FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by 1st coordinate of lower left corner
-SELECT * FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by 2nd coordinate or upper right corner
-SELECT * FROM test_cube ORDER BY c~>1 DESC LIMIT 15; -- descending by 1st coordinate of lower left corner
-SELECT * FROM test_cube ORDER BY c~>4 DESC LIMIT 15; -- descending by 2nd coordinate or upper right corner
-
--- same thing for index with points
-CREATE TABLE test_point(c cube);
-INSERT INTO test_point(SELECT cube(array[c->1,c->2,c->3,c->4]) FROM test_cube);
-CREATE INDEX ON test_point USING gist(c);
-SELECT * FROM test_point ORDER BY c~>1, c~>2 LIMIT 15; -- ascending by 1st then by 2nd coordinate
-SELECT * FROM test_point ORDER BY c~>4 DESC LIMIT 15; -- descending by 1st coordinate
+-- Test sorting by coordinates
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+
+-- Same queries with sequential scan (should give the same results as above)
+RESET enable_seqscan;
+SET enable_indexscan = OFF;
+SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
+SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
+SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
+SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound
+SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound
+SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound
+SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound
+RESET enable_indexscan;
diff --git a/doc/src/sgml/cube.sgml b/doc/src/sgml/cube.sgml
index 1ffc40f1a5..3d503719c5 100644
--- a/doc/src/sgml/cube.sgml
+++ b/doc/src/sgml/cube.sgml
@@ -186,10 +186,11 @@
       <entry><literal>a ~&gt; n</></entry>
       <entry><type>float8</></entry>
       <entry>
-        Get <replaceable>n</>-th coordinate in <quote>normalized</> cube
-        representation, in which the coordinates have been rearranged into
-        the form <quote>lower left &mdash; upper right</>; that is, the
-        smaller endpoint along each dimension appears first.
+        Get <replaceable>n</>-th coordinate of cube in following way:
+        n = 2 * k - 1 means lower bound of <replaceable>k</>-th
+        dimension, n = 2 * k means upper bound of
+        <replaceable>k</>-th dimension.  This operator is designed
+        for KNN-GiST support.
       </entry>
      </row>
 
#26Teodor Sigaev
teodor@sigaev.ru
In reply to: Alexander Korotkov (#25)
Re: CUBE seems a bit confused about ORDER BY

Thanks to all, pushed

I think this is useful enough.О©╫ The fact remains that we can't check
very well for functions; maybe suggest a LIKE clause to look for ~>
anywhere in function source code?Seems, it's overengineering. It could give a false positive results and doesn't

cover all cases such as C-functions, library functions for perl/python/etc
languages.

--
Teodor Sigaev E-mail: teodor@sigaev.ru
WWW: http://www.sigaev.ru/

#27Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Teodor Sigaev (#26)
Re: CUBE seems a bit confused about ORDER BY

Teodor Sigaev wrote:

I think this is useful enough.� The fact remains that we can't check
very well for functions; maybe suggest a LIKE clause to look for ~>
anywhere in function source code?

Seems, it's overengineering. It could give a false positive results
and doesn't cover all cases such as C-functions, library functions for
perl/python/etc languages.

That's true, and I agree we don't necessarily have to find everything.
I still think we should print the pg_depend query in the relnotes,
because those would be the most common cases of objects that need to be
rebuilt.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#28Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#27)
Re: CUBE seems a bit confused about ORDER BY

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

That's true, and I agree we don't necessarily have to find everything.
I still think we should print the pg_depend query in the relnotes,
because those would be the most common cases of objects that need to be
rebuilt.

Wait. A. Minute. This patch seems totally unacceptable for backpatching.

Since behavior of ~> (cube, int) operator is changed, depending entities
must be refreshed after upgrade. Such as, expression indexes using this
operator must be reindexed, materialized views must be rebuilt, stored
procedures and client code must be revised to correctly use new behavior.
That should be mentioned in release notes.

Was there any real discussion of whether we could get away with that
in the back branches? My opinion is no. It's not even clear to me
that this is acceptable in HEAD --- isn't it going to create huge
problems for pg_upgrade?

Perhaps some solution to the compatibility problems could be found
based on giving the cube extension a new version number, and applying
the behavioral change only upon doing ALTER EXTENSION UPDATE. But
it doesn't look like the patch even bumped the extension's version
number.

I do not think this patch was ready to commit.

regards, tom lane

#29Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Tom Lane (#28)
Re: CUBE seems a bit confused about ORDER BY

Tom Lane wrote:

Since behavior of ~> (cube, int) operator is changed, depending entities
must be refreshed after upgrade. Such as, expression indexes using this
operator must be reindexed, materialized views must be rebuilt, stored
procedures and client code must be revised to correctly use new behavior.
That should be mentioned in release notes.

Was there any real discussion of whether we could get away with that
in the back branches? My opinion is no. It's not even clear to me
that this is acceptable in HEAD --- isn't it going to create huge
problems for pg_upgrade?

This was discussed upthread and the solution found was "objects need to
be rebuilt, indexes need to be reindexed". The point of Alexander's
query was to find affected objects that need such treatment. Teodor
explicitly disregarded any change in pg_upgrade because the database
you're upgrading *from* is supposed to have gotten indexes reindexed,
etc.

Perhaps some solution to the compatibility problems could be found
based on giving the cube extension a new version number, and applying
the behavioral change only upon doing ALTER EXTENSION UPDATE.

Hmm, that's an idea worth exploring, keeping in mind that the bug
affects an operator. I'm not sure it's possible to redefine an operator
(change its underlying function) without dropping it, which would cause
its OID to change. Maybe that's okay, or maybe there's a way around it.

But it doesn't look like the patch even bumped the extension's version
number.

As I understand, extension version numbers should change if the SQL
interface changes (the set of objects differ), but should not change if
only the underlying C code changes.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#30Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#29)
Re: CUBE seems a bit confused about ORDER BY

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

Tom Lane wrote:

Was there any real discussion of whether we could get away with that
in the back branches? My opinion is no. It's not even clear to me
that this is acceptable in HEAD --- isn't it going to create huge
problems for pg_upgrade?

This was discussed upthread and the solution found was "objects need to
be rebuilt, indexes need to be reindexed". The point of Alexander's
query was to find affected objects that need such treatment. Teodor
explicitly disregarded any change in pg_upgrade because the database
you're upgrading *from* is supposed to have gotten indexes reindexed,
etc.

I don't think that is really going to be acceptable. People do not like
minor version updates that break their databases. If we start doing
that we're going to find people refusing to apply minor updates, which
is not a place we want to be.

What we've done in the past for comparable situations is to make the
change in a new major version and teach pg_upgrade to detect and report
the need for changes --- in this case, it might do something like create
a script of REINDEX commands for the affected indexes. See e.g.
old_9_6_invalidate_hash_indexes().

regards, tom lane

#31Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Tom Lane (#30)
Re: CUBE seems a bit confused about ORDER BY

Tom Lane wrote:

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

This was discussed upthread and the solution found was "objects need to
be rebuilt, indexes need to be reindexed". The point of Alexander's
query was to find affected objects that need such treatment. Teodor
explicitly disregarded any change in pg_upgrade because the database
you're upgrading *from* is supposed to have gotten indexes reindexed,
etc.

I don't think that is really going to be acceptable. People do not like
minor version updates that break their databases. If we start doing
that we're going to find people refusing to apply minor updates, which
is not a place we want to be.

I was uncomfortable with the idea of not doing anything to fix old
databases.

What we've done in the past for comparable situations is to make the
change in a new major version and teach pg_upgrade to detect and report
the need for changes --- in this case, it might do something like create
a script of REINDEX commands for the affected indexes. See e.g.
old_9_6_invalidate_hash_indexes().

I think the whole point is that any knn-gist indexes using this operator
are completely broken (i.e. they return the wrong answer), so leaving
them as-is in existing branches is not cool.

The idea of an extension update being the trigger for a fix sounds
reasonable. Maybe we can have the current function under ~> that throws
a WARNING each time it is invoked, inviting people to upgrade the
extension; and the new extension would contain a new ~> with the right
semantics. Then all the magic to rebuild objects has to occur in the
upgrade .sql script.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#32Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Alvaro Herrera (#31)
Re: CUBE seems a bit confused about ORDER BY

On Thu, Jan 11, 2018 at 10:29 PM, Alvaro Herrera <alvherre@alvh.no-ip.org>
wrote:

Tom Lane wrote:

What we've done in the past for comparable situations is to make the
change in a new major version and teach pg_upgrade to detect and report
the need for changes --- in this case, it might do something like create
a script of REINDEX commands for the affected indexes. See e.g.
old_9_6_invalidate_hash_indexes().

I think the whole point is that any knn-gist indexes using this operator
are completely broken (i.e. they return the wrong answer), so leaving
them as-is in existing branches is not cool.

The idea of an extension update being the trigger for a fix sounds
reasonable. Maybe we can have the current function under ~> that throws
a WARNING each time it is invoked, inviting people to upgrade the
extension; and the new extension would contain a new ~> with the right
semantics. Then all the magic to rebuild objects has to occur in the
upgrade .sql script.

Yes, the point is that ~> operator was especially designed to get knn-gist
support.
However, its design was broken. And operator with that behavior can't be
accelerated using knn-gist. We could leave this operator "as is", but drop
its
knn-gist support. But that would be discouraging since then ~> operator
would
be left almost useless. Assuming that ~> operator seems to not being
heavily
used (bug report was received after more than year after release), I
proposed
to change operator behavior so that it can be accelerated by knn-gist.

I like Alvaro's proposal about extension upgrade. Assuming that there are
not
many users of ~>, and even smaller amount of them has built depending
objects
over ~> operator, I'd like to propose to not invent magic in upgrade .sql
script.
In .sql script can just do DROP OPERATOR, and CREATE OPERATOR with
new function. If depending objects exist then this script will trigger an
error.
Given this error, user can manually drop depending objects before entension
upgrade. Anyway, assuming that behavior of ~> operator was changed,
depending
objects need to be adjusted not just rebuilt. So, magic would unlikely
work in
this case.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#33Teodor Sigaev
teodor@sigaev.ru
In reply to: Tom Lane (#30)
Re: CUBE seems a bit confused about ORDER BY

This was discussed upthread and the solution found was "objects need to
be rebuilt, indexes need to be reindexed". The point of Alexander's
query was to find affected objects that need such treatment. Teodor
explicitly disregarded any change in pg_upgrade because the database
you're upgrading *from* is supposed to have gotten indexes reindexed,
etc.

I don't think that is really going to be acceptable. People do not like
minor version updates that break their databases. If we start doing
that we're going to find people refusing to apply minor updates, which
is not a place we want to be.

That's true, but we have choice of bad solutions. Current index could
not support operator before patch. So we can:
1) Change operator to support existing index. That is what Alexander
did. And yes, it changes returning order for both sequential and
index scans, but makes them synced. Actually, user should not
reindex existing indexes but should be ready for order changing
2) Change index structure which isn't obvious how. At least, it's
possible to add new operator class (so, upgrade script is needed)
Mandatory reindex and order changes for index scans
3) Remove index support for this operator at all. And introduce new
operator in HEAD with index support. This will need an upgrade script
in minor versions

--
Teodor Sigaev E-mail: teodor@sigaev.ru
WWW: http://www.sigaev.ru/