From febf8d64154bdd40997398fb0ff79c27b726fc72 Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekewurm+postgres@gmail.com>
Date: Wed, 10 Dec 2025 18:45:19 +0100
Subject: [PATCH v2] Add SQL-level datum equality tests

This enables improved performance for users that need to test for
exact bytewise differences in SQL; e.g. to see if an UPDATE is
really necessary with a set of externally provided values.

A workaround around this limitation was often possible, but no
generic method was available that was this performant, or
worked for all types.
---
 doc/src/sgml/func/func-comparison.sgml   | 21 ++++++++++
 src/backend/utils/adt/pseudotypes.c      | 51 ++++++++++++++++++++++++
 src/include/catalog/pg_proc.dat          |  7 ++++
 src/test/regress/expected/opr_sanity.out |  1 +
 4 files changed, 80 insertions(+)

diff --git a/doc/src/sgml/func/func-comparison.sgml b/doc/src/sgml/func/func-comparison.sgml
index ecb1d89463a..2f970fecda3 100644
--- a/doc/src/sgml/func/func-comparison.sgml
+++ b/doc/src/sgml/func/func-comparison.sgml
@@ -653,6 +653,27 @@ SELECT NOT(ROW(table.*) IS NOT NULL) FROM TABLE; -- detect at least one null in
         <returnvalue>1</returnvalue>
        </para></entry>
       </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_datum_image_equal</primary>
+        </indexterm>
+        <function>pg_datum_image_equal</function> ( <type>anyelement</type>, <type>anyelement</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Returns whether the values have the same exact binary representation.
+       </para>
+       <para>
+        <literal>pg_datum_image_equal('1.0'::numeric, '1.0'::numeric)</literal>
+        <returnvalue>true</returnvalue>
+       </para>
+       <para>
+        <literal>pg_datum_image_equal('1.0'::numeric, '1.00'::numeric)</literal>
+        <returnvalue>false</returnvalue>
+       </para></entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 4581c4b1697..93a67e53026 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -23,7 +23,9 @@
 #include "postgres.h"
 
 #include "libpq/pqformat.h"
+#include "utils/datum.h"
 #include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
 
 
 /*
@@ -375,3 +377,52 @@ PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anynonarray);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anycompatible);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anycompatiblenonarray);
+
+/*
+ * Compares two datums of the same (any) type, and returns whether they have
+ * the same binary representation.
+ */
+Datum
+pg_datum_image_equal(PG_FUNCTION_ARGS)
+{
+	bool		eq;
+
+	if (PG_ARGISNULL(0) != PG_ARGISNULL(1))
+	{
+		eq = false;
+	}
+	else if (PG_ARGISNULL(0))
+	{
+		/* both NULL */
+		eq = true;
+	}
+	else
+	{
+		Oid		typ;
+		Datum	arg0;
+		Datum	arg1;
+		bool	typbyval;
+		char	typalign;
+		int16	typlen;
+
+		typ = get_fn_expr_argtype(fcinfo->flinfo, 0);
+
+		if (!OidIsValid(typ))
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("could not determine type")));
+		}
+
+		Assert(typ == get_fn_expr_argtype(fcinfo->flinfo, 1));
+
+		arg0 = PG_GETARG_DATUM(0);
+		arg1 = PG_GETARG_DATUM(1);
+
+		get_typlenbyvalalign(typ, &typlen, &typbyval, &typalign);
+
+		eq = datum_image_eq(arg0, arg1, typbyval, typlen);
+	}
+
+	PG_RETURN_BOOL(eq);
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0118e970dda..ea59ede9660 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12851,4 +12851,11 @@
   proname => 'hashoid8extended', prorettype => 'int8',
   proargtypes => 'oid8 int8', prosrc => 'hashoid8extended' },
 
+{ oid => '9200',
+  descr => 'test if two values have the same binary representation',
+  proname => 'pg_datum_image_equal', proisstrict => 'f',
+  proleakproof => 't', prorettype => 'bool',
+  proargtypes => 'anyelement anyelement',
+  prosrc => 'pg_datum_image_equal' },
+
 ]
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 6ff4d7ee901..df25551c48d 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -887,6 +887,7 @@ oid8le(oid8,oid8)
 oid8gt(oid8,oid8)
 oid8ge(oid8,oid8)
 btoid8cmp(oid8,oid8)
+pg_datum_image_equal(anyelement,anyelement)
 -- Check that functions without argument are not marked as leakproof.
 SELECT p1.oid::regprocedure
 FROM pg_proc p1 JOIN pg_namespace pn
-- 
2.50.1 (Apple Git-155)

