1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-11 20:28:21 +03:00

Fix behavior of ~> (cube, int) operator

~> (cube, int) operator was especially designed for knn-gist search.
However, it appears that knn-gist search can't work correctly with current
behavior of this operator when dataset contains cubes of variable
dimensionality. In this case, the same value of second operator argument
can point to different dimension depending on dimensionality of particular cube.
Such behavior is incompatible with gist indexing of cubes, and knn-gist doesn't
work correctly for it.

This patch changes behavior of ~> (cube, int) operator by introducing dimension
numbering where value of second argument unambiguously identifies number of
dimension. With new behavior, this operator can be correctly supported by
knn-gist. Relevant changes to cube operator class are also included.

Backpatch to v9.6 where operator was introduced.

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.

Noticed by: Tomas Vondra
Author: Alexander Korotkov
Reviewed by: Tomas Vondra, Andrey Borodin
Discussion: https://www.postgresql.org/message-id/flat/a9657f6a-b497-36ff-e56-482a2c7e3292@2ndquadrant.com
This commit is contained in:
Teodor Sigaev
2018-01-11 14:43:13 +03:00
parent 1226051948
commit bda5281fdc
7 changed files with 901 additions and 527 deletions

View File

@ -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. */