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
From 98a8111a83a3443287061af9461b3e03e9dcb6e6 Mon Sep 17 00:00:00 2001
From: Kirill Panin <kipa...@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

Reply via email to