Binary operators for cubes
Hi hackers!
The "cube" extention is frequently used for vectors, but the current
implementation lacks support for binary operators, such as +, -, *, /.
The attached (fairly trivial) patch adds support for these with the
required documentation and test changes.
Best Regards,
Kirill Panin
Attachments:
v1-0001-Add-binary-operators-for-cubes.patchtext/x-patch; charset=UTF-8; name=v1-0001-Add-binary-operators-for-cubes.patchDownload
From 98a8111a83a3443287061af9461b3e03e9dcb6e6 Mon Sep 17 00:00:00 2001
From: Kirill Panin <kipanin@edu.hse.ru>
Date: Wed, 14 May 2025 15:30:48 +0300
Subject: [PATCH v1] Add binary operators for cubes
---
contrib/cube/Makefile | 2 +-
contrib/cube/cube--1.5--1.6.sql | 56 ++++++++++++++
contrib/cube/cube.c | 128 ++++++++++++++++++++++++++++++++
contrib/cube/cube.control | 2 +-
contrib/cube/expected/cube.out | 61 +++++++++++++++
contrib/cube/meson.build | 1 +
contrib/cube/sql/cube.sql | 12 +++
doc/src/sgml/cube.sgml | 50 +++++++++++++
8 files changed, 310 insertions(+), 2 deletions(-)
create mode 100644 contrib/cube/cube--1.5--1.6.sql
diff --git a/contrib/cube/Makefile b/contrib/cube/Makefile
index dfb0d806e4b..ff08d17903f 100644
--- a/contrib/cube/Makefile
+++ b/contrib/cube/Makefile
@@ -9,7 +9,7 @@ OBJS = \
EXTENSION = cube
DATA = cube--1.2.sql cube--1.2--1.3.sql cube--1.3--1.4.sql cube--1.4--1.5.sql \
- cube--1.1--1.2.sql cube--1.0--1.1.sql
+ cube--1.1--1.2.sql cube--1.0--1.1.sql cube--1.5--1.6.sql
PGFILEDESC = "cube - multidimensional cube data type"
HEADERS = cubedata.h
diff --git a/contrib/cube/cube--1.5--1.6.sql b/contrib/cube/cube--1.5--1.6.sql
new file mode 100644
index 00000000000..9eb21f174e2
--- /dev/null
+++ b/contrib/cube/cube--1.5--1.6.sql
@@ -0,0 +1,56 @@
+/* contrib/cube/cube--1.5--1.6.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION cube UPDATE TO '1.6'" to load this file. \quit
+
+CREATE FUNCTION cube_add(cube, cube)
+RETURNS cube
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+CREATE FUNCTION cube_sub(cube, cube)
+RETURNS cube
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+CREATE FUNCTION cube_mul_cf(cube, float8)
+RETURNS cube
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+CREATE FUNCTION cube_mul_fc(float8, cube)
+RETURNS cube
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+CREATE FUNCTION cube_div(cube, float8)
+RETURNS cube
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
+
+-- Add coordinate-wise binary operators
+
+CREATE OPERATOR + (
+ LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_add,
+ COMMUTATOR = '+'
+);
+
+CREATE OPERATOR - (
+ LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_sub
+);
+
+-- Add coordinate-wise binary operators with scalars
+
+CREATE OPERATOR / (
+ LEFTARG = cube, RIGHTARG = float8, PROCEDURE = cube_div
+);
+
+CREATE OPERATOR * (
+ LEFTARG = cube, RIGHTARG = float8, PROCEDURE = cube_mul_cf,
+ COMMUTATOR = '*'
+);
+
+CREATE OPERATOR * (
+ LEFTARG = float8, RIGHTARG = cube, PROCEDURE = cube_mul_fc,
+ COMMUTATOR = '*'
+);
diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
index 8d3654ab7aa..47e568cd958 100644
--- a/contrib/cube/cube.c
+++ b/contrib/cube/cube.c
@@ -91,6 +91,11 @@ PG_FUNCTION_INFO_V1(cube_distance);
PG_FUNCTION_INFO_V1(distance_chebyshev);
PG_FUNCTION_INFO_V1(cube_is_point);
PG_FUNCTION_INFO_V1(cube_enlarge);
+PG_FUNCTION_INFO_V1(cube_add);
+PG_FUNCTION_INFO_V1(cube_sub);
+PG_FUNCTION_INFO_V1(cube_div);
+PG_FUNCTION_INFO_V1(cube_mul_cf);
+PG_FUNCTION_INFO_V1(cube_mul_fc);
/*
** For internal use only
@@ -109,6 +114,8 @@ bool g_cube_internal_consistent(NDBOX *key, NDBOX *query, StrategyNumber strate
*/
static double distance_1D(double a1, double a2, double b1, double b2);
static bool cube_is_point_internal(NDBOX *cube);
+static NDBOX *cube_binop_helper(NDBOX *a, NDBOX *b);
+static NDBOX *cube_alloc_shape(NDBOX *a);
/*****************************************************************************
@@ -1911,3 +1918,124 @@ cube_c_f8_f8(PG_FUNCTION_ARGS)
PG_FREE_IF_COPY(cube, 0);
PG_RETURN_NDBOX_P(result);
}
+
+static NDBOX *
+cube_alloc_shape(NDBOX *a) {
+ NDBOX *result;
+ int dim = DIM(a);
+ int size;
+
+ if (IS_POINT(a)) {
+ size = POINT_SIZE(dim);
+ result = (NDBOX *) palloc0(size);
+ SET_POINT_BIT(result);
+ } else {
+ size = CUBE_SIZE(dim);
+ result = (NDBOX *) palloc0(size);
+ }
+
+ SET_VARSIZE(result, size);
+ SET_DIM(result, dim);
+
+ return result;
+}
+
+NDBOX *
+cube_binop_helper(NDBOX *a, NDBOX *b)
+{
+ if (DIM(a) != DIM(b))
+ ereport(ERROR,
+ (errcode(ERRCODE_CARDINALITY_VIOLATION),
+ errmsg("cubes have different lengths: %d != %d", DIM(a), DIM(b))));
+
+ if (IS_POINT(a) != IS_POINT(b))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_EXCEPTION),
+ errmsg("it's POINTless to add these cubes: %d != %d", IS_POINT(a), IS_POINT(b))));
+
+ return cube_alloc_shape(a);
+}
+
+/*
+ * Function returns coordinate-wise sum of cubes.
+ */
+Datum
+cube_add(PG_FUNCTION_ARGS)
+{
+ NDBOX *a = PG_GETARG_NDBOX_P(0);
+ NDBOX *b = PG_GETARG_NDBOX_P(1);
+ NDBOX *result = cube_binop_helper(a, b);
+ int i;
+ int n = DIM(a) * (IS_POINT(a) ? 1 : 2);
+
+ for (i = 0; i < n; i++)
+ result->x[i] = a->x[i] + b->x[i];
+
+ PG_RETURN_NDBOX_P(result);
+}
+
+/*
+ * Function returns coordinate-wise difference of cubes.
+ */
+Datum
+cube_sub(PG_FUNCTION_ARGS)
+{
+ NDBOX *a = PG_GETARG_NDBOX_P(0);
+ NDBOX *b = PG_GETARG_NDBOX_P(1);
+ NDBOX *result = cube_binop_helper(a, b);
+ int i;
+ int n = DIM(a) * (IS_POINT(a) ? 1 : 2);
+
+ for (i = 0; i < n; i++)
+ result->x[i] = a->x[i] - b->x[i];
+
+ PG_RETURN_NDBOX_P(result);
+}
+
+/*
+ * Functions return scaled cube.
+ */
+Datum
+cube_div(PG_FUNCTION_ARGS)
+{
+ NDBOX *a = PG_GETARG_NDBOX_P(0);
+ double s = PG_GETARG_FLOAT8(1);
+ NDBOX *result = cube_alloc_shape(a);
+ int i;
+ int n = DIM(a) * (IS_POINT(a) ? 1 : 2);
+
+ for (i = 0; i < n; i++)
+ result->x[i] = a->x[i] / s;
+
+ PG_RETURN_NDBOX_P(result);
+}
+
+Datum
+cube_mul_cf(PG_FUNCTION_ARGS)
+{
+ NDBOX *a = PG_GETARG_NDBOX_P(0);
+ double s = PG_GETARG_FLOAT8(1);
+ NDBOX *result = cube_alloc_shape(a);
+ int i;
+ int n = DIM(a) * (IS_POINT(a) ? 1 : 2);
+
+ for (i = 0; i < n; i++)
+ result->x[i] = a->x[i] * s;
+
+ PG_RETURN_NDBOX_P(result);
+}
+
+Datum
+cube_mul_fc(PG_FUNCTION_ARGS)
+{
+ double s = PG_GETARG_FLOAT8(0);
+ NDBOX *a = PG_GETARG_NDBOX_P(1);
+ NDBOX *result = cube_alloc_shape(a);
+ int i;
+ int n = DIM(a) * (IS_POINT(a) ? 1 : 2);
+
+ for (i = 0; i < n; i++)
+ result->x[i] = a->x[i] * s;
+
+ PG_RETURN_NDBOX_P(result);
+}
diff --git a/contrib/cube/cube.control b/contrib/cube/cube.control
index 50427ec1170..7797f7e08d4 100644
--- a/contrib/cube/cube.control
+++ b/contrib/cube/cube.control
@@ -1,6 +1,6 @@
# cube extension
comment = 'data type for multidimensional cubes'
-default_version = '1.5'
+default_version = '1.6'
module_pathname = '$libdir/cube'
relocatable = true
trusted = true
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
index 47787c50bd9..698750955de 100644
--- a/contrib/cube/expected/cube.out
+++ b/contrib/cube/expected/cube.out
@@ -1973,3 +1973,64 @@ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upp
(15 rows)
RESET enable_indexscan;
+-- Test of binary operators
+SELECT '(-7,7)'::cube + '(-1,1)'::cube;
+ ?column?
+----------
+ (-8, 8)
+(1 row)
+
+SELECT '(-7,7)'::cube - '(-1,1)'::cube;
+ ?column?
+----------
+ (-6, 6)
+(1 row)
+
+SELECT '(-1,2)'::cube * 2;
+ ?column?
+----------
+ (-2, 4)
+(1 row)
+
+SELECT '(-2,4)'::cube / 2;
+ ?column?
+----------
+ (-1, 2)
+(1 row)
+
+SELECT 2 * '(-1,2)'::cube;
+ ?column?
+----------
+ (-2, 4)
+(1 row)
+
+SELECT '(-7,7),(2,9)'::cube + '(-1,1),(5,12)'::cube;
+ ?column?
+-----------------
+ (-8, 8),(7, 21)
+(1 row)
+
+SELECT '(-7,7),(2,9)'::cube - '(-1,1),(5,12)'::cube;
+ ?column?
+------------------
+ (-6, 6),(-3, -3)
+(1 row)
+
+SELECT '(-1,2),(2,9)'::cube * 2;
+ ?column?
+-----------------
+ (-2, 4),(4, 18)
+(1 row)
+
+SELECT '(-2,4),(3,7)'::cube / 2;
+ ?column?
+--------------------
+ (-1, 2),(1.5, 3.5)
+(1 row)
+
+SELECT 2 * '(-1,2),(2,9)'::cube;
+ ?column?
+-----------------
+ (-2, 4),(4, 18)
+(1 row)
+
diff --git a/contrib/cube/meson.build b/contrib/cube/meson.build
index fd3c057f469..32d88a0416e 100644
--- a/contrib/cube/meson.build
+++ b/contrib/cube/meson.build
@@ -40,6 +40,7 @@ install_data(
'cube--1.2--1.3.sql',
'cube--1.3--1.4.sql',
'cube--1.4--1.5.sql',
+ 'cube--1.5--1.6.sql',
kwargs: contrib_data_args,
)
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
index eec90d21ee3..e819eacbfe8 100644
--- a/contrib/cube/sql/cube.sql
+++ b/contrib/cube/sql/cube.sql
@@ -436,3 +436,15 @@ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by rig
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;
+
+-- Test of binary operators
+SELECT '(-7,7)'::cube + '(-1,1)'::cube;
+SELECT '(-7,7)'::cube - '(-1,1)'::cube;
+SELECT '(-1,2)'::cube * 2;
+SELECT '(-2,4)'::cube / 2;
+SELECT 2 * '(-1,2)'::cube;
+SELECT '(-7,7),(2,9)'::cube + '(-1,1),(5,12)'::cube;
+SELECT '(-7,7),(2,9)'::cube - '(-1,1),(5,12)'::cube;
+SELECT '(-1,2),(2,9)'::cube * 2;
+SELECT '(-2,4),(3,7)'::cube / 2;
+SELECT 2 * '(-1,2),(2,9)'::cube;
diff --git a/doc/src/sgml/cube.sgml b/doc/src/sgml/cube.sgml
index 0fb70807486..19355a1041f 100644
--- a/doc/src/sgml/cube.sgml
+++ b/doc/src/sgml/cube.sgml
@@ -218,6 +218,56 @@
Computes the Chebyshev (L-inf metric) distance between the two cubes.
</para></entry>
</row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>cube</type> <literal>+</literal> <type>cube</type>
+ <returnvalue>cube</returnvalue>
+ </para>
+ <para>
+ Computes the coordinate-wise sum of two cubes.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>cube</type> <literal>-</literal> <type>cube</type>
+ <returnvalue>cube</returnvalue>
+ </para>
+ <para>
+ Computes the coordinate-wise difference of two cubes.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>cube</type> <literal>*</literal> <type>float8</type>
+ <returnvalue>cube</returnvalue>
+ </para>
+ <para>
+ Computes the coordinate-wise multiplication of a cube by a scalar value.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>float8</type> <literal>*</literal> <type>cube</type>
+ <returnvalue>cube</returnvalue>
+ </para>
+ <para>
+ Computes the coordinate-wise multiplication of a cube by a scalar value.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>cube</type> <literal>/</literal> <type>float8</type>
+ <returnvalue>cube</returnvalue>
+ </para>
+ <para>
+ Computes the coordinate-wise division of a cube by a scalar value.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
base-commit: 2c0ed86d393670c7054d051490063de771f1791e
--
2.49.0
On Thu, 15 May 2025 at 16:16, Kirill Panin <kipanin@edu.hse.ru> wrote:
Hi hackers!
The "cube" extention is frequently used for vectors, but the current
implementation lacks support for binary operators, such as +, -, *, /.
The attached (fairly trivial) patch adds support for these with the
required documentation and test changes.
I don't think that your definition of addition and subtraction makes
sense for type cube. The docs say:
It does not matter which order the opposite corners of a cube are
entered in. The cube functions automatically swap values if needed to
create a uniform “lower left — upper right” internal representation.
Thus, for example, the following 2 cubes are equal:
SELECT '(0,0), (10,10)'::cube = '(10,0), (0,10)'::cube;
?column?
----------
t
(1 row)
However, with your definition of addition in terms of simple pointwise
addition of coordinates, the results of adding this cube to another
are different, depending on the order of the corners:
SELECT '(0,0), (10,10)'::cube + '(1,2), (3,4)'::cube,
'(10,0), (0,10)'::cube + '(1,2), (3,4)'::cube;
?column? | ?column?
-----------------+-----------------
(1, 2),(13, 14) | (11, 2),(3, 14)
(1 row)
which are not equal. It's a pretty odd form of addition in which a+c
differs from b+c when a and b are the equal.
One could define point+point and point+cube addition to be translation
by the values from the point, and then it would work correctly if the
corners of the cube were reversed. That makes a certain amount of
geometrical sense, since then multiplication and addition are just
scale and translate.
I imagine that people using the cube extension for vectors are using
zero-volume cubes (points), so translation-addition would work the
same as normal vector addition in that case. However, I doubt that
cube is ever going to make a good general-purpose type for vectors. It
would be somewhat odd to allow dot and cross products of cubes, for
example.
Regards,
Dean
Hi,
Thanks for your response.
Do you think it makes sense to disallow cube+cube operations and only
allow cube+point or point+point instead? Dot and cross products would be
similarly only defined for point+point if someone were to implement them.
I'll submit a v2 if this is an acceptable solution.
Regards,
Kirill
Show quoted text
On 7/2/25 13:57, Dean Rasheed wrote:
On Thu, 15 May 2025 at 16:16, Kirill Panin <kipanin@edu.hse.ru> wrote:
Hi hackers!
The "cube" extention is frequently used for vectors, but the current
implementation lacks support for binary operators, such as +, -, *, /.
The attached (fairly trivial) patch adds support for these with the
required documentation and test changes.I don't think that your definition of addition and subtraction makes
sense for type cube. The docs say:It does not matter which order the opposite corners of a cube are
entered in. The cube functions automatically swap values if needed to
create a uniform “lower left — upper right” internal representation.Thus, for example, the following 2 cubes are equal:
SELECT '(0,0), (10,10)'::cube = '(10,0), (0,10)'::cube;
?column?
----------
t
(1 row)However, with your definition of addition in terms of simple pointwise
addition of coordinates, the results of adding this cube to another
are different, depending on the order of the corners:SELECT '(0,0), (10,10)'::cube + '(1,2), (3,4)'::cube,
'(10,0), (0,10)'::cube + '(1,2), (3,4)'::cube;?column? | ?column?
-----------------+-----------------
(1, 2),(13, 14) | (11, 2),(3, 14)
(1 row)which are not equal. It's a pretty odd form of addition in which a+c
differs from b+c when a and b are the equal.One could define point+point and point+cube addition to be translation
by the values from the point, and then it would work correctly if the
corners of the cube were reversed. That makes a certain amount of
geometrical sense, since then multiplication and addition are just
scale and translate.I imagine that people using the cube extension for vectors are using
zero-volume cubes (points), so translation-addition would work the
same as normal vector addition in that case. However, I doubt that
cube is ever going to make a good general-purpose type for vectors. It
would be somewhat odd to allow dot and cross products of cubes, for
example.Regards,
Dean
On Fri, 4 Jul 2025 at 13:07, Kirill Panin <kipanin@edu.hse.ru> wrote:
Thanks for your response.
Do you think it makes sense to disallow cube+cube operations and only
allow cube+point or point+point instead? Dot and cross products would be
similarly only defined for point+point if someone were to implement them.
I'll submit a v2 if this is an acceptable solution.
Having thought about this a little more, I'm not convinced that doing
any of this is a good idea.
The problem is a cube is not a vector, and adding more functions or
operators to try to make it behave like one seems to be going in the
wrong direction. Perhaps it would help to re-examine the use cases,
and whether using the cube extension is the right thing in the first
place.
Regards,
Dean