From 380e431166a1f87c5bd6e651049e63e2e8334e6f Mon Sep 17 00:00:00 2001
From: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Date: Fri, 5 Jun 2026 19:10:28 +0000
Subject: [PATCH v1 1/2] Remove the refint contrib module.

refint was sample code from the pre-built-in-FK era and has long been
documented as superseded by the built-in foreign key mechanism.  Recent
fixes (cascade-UPDATE crash with NULL keys, removal of the broken private
SPI plan cache) made it clear that the code has more issues than its
sample-code value justifies.

Remove the C source, control file, SQL script, example file, and
regression test from contrib/spi/.  Adjust the contrib/spi Makefile and
meson.build, drop the refint section from contrib-spi.sgml, and drop the
now-unused EPlan typedef from typedefs.list.

The other contrib/spi extensions (autoinc, insert_username, moddatetime)
are unaffected.

Discussion: https://postgr.es/m/CAJTYsWXU%2BfhuzrEd_bnrxyGH3%2Bny8QRQC2QHf3ws6s9iki3c2Q%40mail.gmail.com
---
 contrib/spi/Makefile             |  13 +-
 contrib/spi/expected/refint.out  | 113 -------
 contrib/spi/meson.build          |  30 --
 contrib/spi/refint--1.0.sql      |  14 -
 contrib/spi/refint.c             | 538 -------------------------------
 contrib/spi/refint.control       |   5 -
 contrib/spi/refint.example       |  82 -----
 contrib/spi/sql/refint.sql       |  97 ------
 doc/src/sgml/contrib-spi.sgml    |  51 ---
 src/tools/pgindent/typedefs.list |   1 -
 10 files changed, 4 insertions(+), 940 deletions(-)
 delete mode 100644 contrib/spi/expected/refint.out
 delete mode 100644 contrib/spi/refint--1.0.sql
 delete mode 100644 contrib/spi/refint.c
 delete mode 100644 contrib/spi/refint.control
 delete mode 100644 contrib/spi/refint.example
 delete mode 100644 contrib/spi/sql/refint.sql

diff --git a/contrib/spi/Makefile b/contrib/spi/Makefile
index 7ccbef8c926..805f7653e58 100644
--- a/contrib/spi/Makefile
+++ b/contrib/spi/Makefile
@@ -1,23 +1,18 @@
 # contrib/spi/Makefile
 
-MODULES = autoinc insert_username moddatetime refint
+MODULES = autoinc insert_username moddatetime
 
-EXTENSION = autoinc insert_username moddatetime refint
+EXTENSION = autoinc insert_username moddatetime
 
 DATA = autoinc--1.0.sql \
        insert_username--1.0.sql \
-       moddatetime--1.0.sql \
-       refint--1.0.sql
+       moddatetime--1.0.sql
 PGFILEDESC = "spi - examples of using SPI and triggers"
 
-REGRESS = autoinc refint
+REGRESS = autoinc
 
 DOCS = $(addsuffix .example, $(MODULES))
 
-# this is needed for the regression tests;
-# comment out if you want a quieter refint package for other uses
-PG_CPPFLAGS = -DREFINT_VERBOSE
-
 ifdef USE_PGXS
 PG_CONFIG = pg_config
 PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/spi/expected/refint.out b/contrib/spi/expected/refint.out
deleted file mode 100644
index 79633603217..00000000000
--- a/contrib/spi/expected/refint.out
+++ /dev/null
@@ -1,113 +0,0 @@
-CREATE EXTENSION refint;
-create table pkeys (pkey1 int4 not null, pkey2 text not null);
-create table fkeys (fkey1 int4, fkey2 text, fkey3 int);
-create table fkeys2 (fkey21 int4, fkey22 text, pkey23 int not null);
-create index fkeys_i on fkeys (fkey1, fkey2);
-create index fkeys2_i on fkeys2 (fkey21, fkey22);
-create index fkeys2p_i on fkeys2 (pkey23);
-insert into pkeys values (10, '1');
-insert into pkeys values (20, '2');
-insert into pkeys values (30, '3');
-insert into pkeys values (40, '4');
-insert into pkeys values (50, '5');
-insert into pkeys values (60, '6');
-create unique index pkeys_i on pkeys (pkey1, pkey2);
---
--- For fkeys:
--- 	(fkey1, fkey2)	--> pkeys (pkey1, pkey2)
--- 	(fkey3)		--> fkeys2 (pkey23)
---
-create trigger check_fkeys_pkey_exist
-	after insert or update on fkeys
-	for each row
-	execute function
-	check_primary_key ('fkey1', 'fkey2', 'pkeys', 'pkey1', 'pkey2');
-create trigger check_fkeys_pkey2_exist
-	after insert or update on fkeys
-	for each row
-	execute function check_primary_key ('fkey3', 'fkeys2', 'pkey23');
---
--- For fkeys2:
--- 	(fkey21, fkey22)	--> pkeys (pkey1, pkey2)
---
-create trigger check_fkeys2_pkey_exist
-	after insert or update on fkeys2
-	for each row
-	execute procedure
-	check_primary_key ('fkey21', 'fkey22', 'pkeys', 'pkey1', 'pkey2');
---
--- For pkeys:
--- 	ON DELETE/UPDATE (pkey1, pkey2) CASCADE:
--- 		fkeys (fkey1, fkey2) and fkeys2 (fkey21, fkey22)
---
-create trigger check_pkeys_fkey_cascade
-	after delete or update on pkeys
-	for each row
-	execute procedure
-	check_foreign_key (2, 'cascade', 'pkey1', 'pkey2',
-	'fkeys', 'fkey1', 'fkey2', 'fkeys2', 'fkey21', 'fkey22');
---
--- For fkeys2:
--- 	ON DELETE/UPDATE (pkey23) RESTRICT:
--- 		fkeys (fkey3)
---
-create trigger check_fkeys2_fkey_restrict
-	after delete or update on fkeys2
-	for each row
-	execute procedure check_foreign_key (1, 'restrict', 'pkey23', 'fkeys', 'fkey3');
-insert into fkeys2 values (10, '1', 1);
-insert into fkeys2 values (30, '3', 2);
-insert into fkeys2 values (40, '4', 5);
-insert into fkeys2 values (50, '5', 3);
--- no key in pkeys
-insert into fkeys2 values (70, '5', 3);
-ERROR:  tuple references non-existent key
-DETAIL:  Trigger "check_fkeys2_pkey_exist" found tuple referencing non-existent key in "pkeys".
-insert into fkeys values (10, '1', 2);
-insert into fkeys values (30, '3', 3);
-insert into fkeys values (40, '4', 2);
-insert into fkeys values (50, '5', 2);
--- no key in pkeys
-insert into fkeys values (70, '5', 1);
-ERROR:  tuple references non-existent key
-DETAIL:  Trigger "check_fkeys_pkey_exist" found tuple referencing non-existent key in "pkeys".
--- no key in fkeys2
-insert into fkeys values (60, '6', 4);
-ERROR:  tuple references non-existent key
-DETAIL:  Trigger "check_fkeys_pkey2_exist" found tuple referencing non-existent key in "fkeys2".
-delete from pkeys where pkey1 = 30 and pkey2 = '3';
-NOTICE:  check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are deleted
-ERROR:  "check_fkeys2_fkey_restrict": tuple is referenced in "fkeys"
-CONTEXT:  SQL statement "delete from fkeys2 where fkey21 = $1 and fkey22 = $2 "
-delete from pkeys where pkey1 = 40 and pkey2 = '4';
-NOTICE:  check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are deleted
-NOTICE:  check_pkeys_fkey_cascade: 1 tuple(s) of fkeys2 are deleted
-update pkeys set pkey1 = 7, pkey2 = '70' where pkey1 = 50 and pkey2 = '5';
-NOTICE:  check_pkeys_fkey_cascade: 1 tuple(s) of fkeys are updated
-NOTICE:  check_pkeys_fkey_cascade: 1 tuple(s) of fkeys2 are updated
-update pkeys set pkey1 = 7, pkey2 = '70' where pkey1 = 10 and pkey2 = '1';
-ERROR:  duplicate key value violates unique constraint "pkeys_i"
-DETAIL:  Key (pkey1, pkey2)=(7, 70) already exists.
-SELECT trigger_name, event_manipulation, event_object_schema, event_object_table,
-       action_order, action_condition, action_orientation, action_timing,
-       action_reference_old_table, action_reference_new_table
-  FROM information_schema.triggers
-  WHERE event_object_table in ('pkeys', 'fkeys', 'fkeys2')
-  ORDER BY trigger_name COLLATE "C", 2;
-        trigger_name        | event_manipulation | event_object_schema | event_object_table | action_order | action_condition | action_orientation | action_timing | action_reference_old_table | action_reference_new_table 
-----------------------------+--------------------+---------------------+--------------------+--------------+------------------+--------------------+---------------+----------------------------+----------------------------
- check_fkeys2_fkey_restrict | DELETE             | public              | fkeys2             |            1 |                  | ROW                | AFTER         |                            | 
- check_fkeys2_fkey_restrict | UPDATE             | public              | fkeys2             |            1 |                  | ROW                | AFTER         |                            | 
- check_fkeys2_pkey_exist    | INSERT             | public              | fkeys2             |            1 |                  | ROW                | AFTER         |                            | 
- check_fkeys2_pkey_exist    | UPDATE             | public              | fkeys2             |            2 |                  | ROW                | AFTER         |                            | 
- check_fkeys_pkey2_exist    | INSERT             | public              | fkeys              |            1 |                  | ROW                | AFTER         |                            | 
- check_fkeys_pkey2_exist    | UPDATE             | public              | fkeys              |            1 |                  | ROW                | AFTER         |                            | 
- check_fkeys_pkey_exist     | INSERT             | public              | fkeys              |            2 |                  | ROW                | AFTER         |                            | 
- check_fkeys_pkey_exist     | UPDATE             | public              | fkeys              |            2 |                  | ROW                | AFTER         |                            | 
- check_pkeys_fkey_cascade   | DELETE             | public              | pkeys              |            1 |                  | ROW                | AFTER         |                            | 
- check_pkeys_fkey_cascade   | UPDATE             | public              | pkeys              |            1 |                  | ROW                | AFTER         |                            | 
-(10 rows)
-
-DROP TABLE pkeys;
-DROP TABLE fkeys;
-DROP TABLE fkeys2;
diff --git a/contrib/spi/meson.build b/contrib/spi/meson.build
index 4a9a2bef0a5..85f0b2276f7 100644
--- a/contrib/spi/meson.build
+++ b/contrib/spi/meson.build
@@ -79,35 +79,6 @@ install_data('moddatetime.example',
 )
 
 
-# this is needed for the regression tests;
-# comment out if you want a quieter refint package for other uses
-refint_cflags = ['-DREFINT_VERBOSE']
-
-refint_sources = files(
-  'refint.c',
-)
-
-if host_system == 'windows'
-  refint_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
-    '--NAME', 'refint',
-    '--FILEDESC', 'spi - examples of using SPI and triggers',])
-endif
-
-refint = shared_module('refint',
-  refint_sources,
-  c_args: refint_cflags,
-  kwargs: contrib_mod_args,
-)
-contrib_targets += refint
-
-install_data('refint.control', 'refint--1.0.sql',
-  kwargs: contrib_data_args,
-)
-
-install_data('refint.example',
-  kwargs: contrib_doc_args,
-)
-
 tests += {
   'name': 'spi',
   'sd': meson.current_source_dir(),
@@ -115,7 +86,6 @@ tests += {
   'regress': {
     'sql': [
       'autoinc',
-      'refint',
     ],
   },
 }
diff --git a/contrib/spi/refint--1.0.sql b/contrib/spi/refint--1.0.sql
deleted file mode 100644
index faf797c2157..00000000000
--- a/contrib/spi/refint--1.0.sql
+++ /dev/null
@@ -1,14 +0,0 @@
-/* contrib/spi/refint--1.0.sql */
-
--- complain if script is sourced in psql, rather than via CREATE EXTENSION
-\echo Use "CREATE EXTENSION refint" to load this file. \quit
-
-CREATE FUNCTION check_primary_key()
-RETURNS trigger
-AS 'MODULE_PATHNAME'
-LANGUAGE C;
-
-CREATE FUNCTION check_foreign_key()
-RETURNS trigger
-AS 'MODULE_PATHNAME'
-LANGUAGE C;
diff --git a/contrib/spi/refint.c b/contrib/spi/refint.c
deleted file mode 100644
index 0bfd963fa83..00000000000
--- a/contrib/spi/refint.c
+++ /dev/null
@@ -1,538 +0,0 @@
-/*
- * contrib/spi/refint.c
- *
- *
- * refint.c --	set of functions to define referential integrity
- *		constraints using general triggers.
- */
-#include "postgres.h"
-
-#include <ctype.h>
-
-#include "commands/trigger.h"
-#include "executor/spi.h"
-#include "utils/builtins.h"
-#include "utils/rel.h"
-
-PG_MODULE_MAGIC_EXT(
-					.name = "refint",
-					.version = PG_VERSION
-);
-
-/*
- * check_primary_key () -- check that key in tuple being inserted/updated
- *			 references existing tuple in "primary" table.
- * Though it's called without args You have to specify referenced
- * table/keys while creating trigger:  key field names in triggered table,
- * referenced table name, referenced key field names:
- * EXECUTE PROCEDURE
- * check_primary_key ('Fkey1', 'Fkey2', 'Ptable', 'Pkey1', 'Pkey2').
- */
-
-PG_FUNCTION_INFO_V1(check_primary_key);
-
-Datum
-check_primary_key(PG_FUNCTION_ARGS)
-{
-	TriggerData *trigdata = (TriggerData *) fcinfo->context;
-	Trigger    *trigger;		/* to get trigger name */
-	int			nargs;			/* # of args specified in CREATE TRIGGER */
-	char	  **args;			/* arguments: column names and table name */
-	int			nkeys;			/* # of key columns (= nargs / 2) */
-	Datum	   *kvals;			/* key values */
-	char	   *relname;		/* referenced relation name */
-	Relation	rel;			/* triggered relation */
-	HeapTuple	tuple = NULL;	/* tuple to return */
-	TupleDesc	tupdesc;		/* tuple description */
-	SPIPlanPtr	pplan;			/* prepared plan */
-	Oid		   *argtypes = NULL;	/* key types to prepare execution plan */
-	bool		isnull;			/* to know is some column NULL or not */
-	int			ret;
-	int			i;
-	StringInfoData sql;
-
-#ifdef	DEBUG_QUERY
-	elog(DEBUG4, "check_primary_key: Enter Function");
-#endif
-
-	/*
-	 * Some checks first...
-	 */
-
-	/* Called by trigger manager ? */
-	if (!CALLED_AS_TRIGGER(fcinfo))
-		/* internal error */
-		elog(ERROR, "check_primary_key: not fired by trigger manager");
-
-	/* Should be called for ROW trigger */
-	if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-		/* internal error */
-		elog(ERROR, "check_primary_key: must be fired for row");
-
-	if (!TRIGGER_FIRED_AFTER(trigdata->tg_event))
-		/* internal error */
-		elog(ERROR, "check_primary_key: must be fired by AFTER trigger");
-
-	/* If INSERTion then must check Tuple to being inserted */
-	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
-		tuple = trigdata->tg_trigtuple;
-
-	/* Not should be called for DELETE */
-	else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
-		/* internal error */
-		elog(ERROR, "check_primary_key: cannot process DELETE events");
-
-	/* If UPDATE, then must check new Tuple, not old one */
-	else
-		tuple = trigdata->tg_newtuple;
-
-	trigger = trigdata->tg_trigger;
-	nargs = trigger->tgnargs;
-	args = trigger->tgargs;
-
-	if (nargs % 2 != 1)			/* odd number of arguments! */
-		/* internal error */
-		elog(ERROR, "check_primary_key: odd number of arguments should be specified");
-
-	nkeys = nargs / 2;
-	relname = args[nkeys];
-	rel = trigdata->tg_relation;
-	tupdesc = rel->rd_att;
-
-	/* Connect to SPI manager */
-	SPI_connect();
-
-	/*
-	 * We use SPI plan preparation feature, so allocate space to place key
-	 * values.
-	 */
-	kvals = (Datum *) palloc(nkeys * sizeof(Datum));
-
-	/* allocate argtypes for preparation */
-	argtypes = (Oid *) palloc(nkeys * sizeof(Oid));
-
-	/* For each column in key ... */
-	for (i = 0; i < nkeys; i++)
-	{
-		/* get index of column in tuple */
-		int			fnumber = SPI_fnumber(tupdesc, args[i]);
-
-		/* Bad guys may give us un-existing column in CREATE TRIGGER */
-		if (fnumber <= 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("there is no attribute \"%s\" in relation \"%s\"",
-							args[i], SPI_getrelname(rel))));
-
-		/* Well, get binary (in internal format) value of column */
-		kvals[i] = SPI_getbinval(tuple, tupdesc, fnumber, &isnull);
-
-		/*
-		 * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
-		 * DON'T FORGET return tuple! Executor inserts tuple you're returning!
-		 * If you return NULL then nothing will be inserted!
-		 */
-		if (isnull)
-		{
-			SPI_finish();
-			return PointerGetDatum(tuple);
-		}
-
-		/* Get typeId of column */
-		argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
-	}
-
-	initStringInfo(&sql);
-
-	/*
-	 * Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 = $1
-	 * [AND Pkey2 = $2 [...]]
-	 */
-	appendStringInfo(&sql, "select 1 from %s where ", relname);
-	for (i = 1; i <= nkeys; i++)
-	{
-		appendStringInfo(&sql, "%s = $%d ", args[i + nkeys], i);
-		if (i < nkeys)
-			appendStringInfoString(&sql, "and ");
-	}
-
-	/* Prepare plan for query */
-	pplan = SPI_prepare(sql.data, nkeys, argtypes);
-	if (pplan == NULL)
-		/* internal error */
-		elog(ERROR, "check_primary_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result));
-
-	pfree(sql.data);
-
-	/*
-	 * Ok, execute prepared plan.
-	 */
-	ret = SPI_execp(pplan, kvals, NULL, 1);
-	/* we have no NULLs - so we pass   ^^^^   here */
-
-	if (ret < 0)
-		/* internal error */
-		elog(ERROR, "check_primary_key: SPI_execp returned %d", ret);
-
-	/*
-	 * If there are no tuples returned by SELECT then ...
-	 */
-	if (SPI_processed == 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
-				 errmsg("tuple references non-existent key"),
-				 errdetail("Trigger \"%s\" found tuple referencing non-existent key in \"%s\".", trigger->tgname, relname)));
-
-	SPI_finish();
-
-	return PointerGetDatum(tuple);
-}
-
-/*
- * check_foreign_key () -- check that key in tuple being deleted/updated
- *			 is not referenced by tuples in "foreign" table(s).
- * Though it's called without args You have to specify (while creating trigger):
- * number of references, action to do if key referenced
- * ('restrict' | 'setnull' | 'cascade'), key field names in triggered
- * ("primary") table and referencing table(s)/keys:
- * EXECUTE PROCEDURE
- * check_foreign_key (2, 'restrict', 'Pkey1', 'Pkey2',
- * 'Ftable1', 'Fkey11', 'Fkey12', 'Ftable2', 'Fkey21', 'Fkey22').
- */
-
-PG_FUNCTION_INFO_V1(check_foreign_key);
-
-Datum
-check_foreign_key(PG_FUNCTION_ARGS)
-{
-	TriggerData *trigdata = (TriggerData *) fcinfo->context;
-	Trigger    *trigger;		/* to get trigger name */
-	int			nargs;			/* # of args specified in CREATE TRIGGER */
-	char	  **args;			/* arguments: as described above */
-	char	  **args_temp;
-	int			nrefs;			/* number of references (== # of plans) */
-	char		action;			/* 'R'estrict | 'S'etnull | 'C'ascade */
-	int			nkeys;			/* # of key columns */
-	Datum	   *kvals;			/* key values */
-	char	   *relname;		/* referencing relation name */
-	Relation	rel;			/* triggered relation */
-	HeapTuple	trigtuple = NULL;	/* tuple to being changed */
-	HeapTuple	newtuple = NULL;	/* tuple to return */
-	TupleDesc	tupdesc;		/* tuple description */
-	SPIPlanPtr *splan;			/* prepared plan(s) */
-	Oid		   *argtypes = NULL;	/* key types to prepare execution plan */
-	bool		isnull;			/* to know is some column NULL or not */
-	bool		isequal = true; /* are keys in both tuples equal (in UPDATE) */
-	int			is_update = 0;
-	int			ret;
-	int			i,
-				r;
-	char	  **args2;
-
-#ifdef DEBUG_QUERY
-	elog(DEBUG4, "check_foreign_key: Enter Function");
-#endif
-
-	/*
-	 * Some checks first...
-	 */
-
-	/* Called by trigger manager ? */
-	if (!CALLED_AS_TRIGGER(fcinfo))
-		/* internal error */
-		elog(ERROR, "check_foreign_key: not fired by trigger manager");
-
-	/* Should be called for ROW trigger */
-	if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-		/* internal error */
-		elog(ERROR, "check_foreign_key: must be fired for row");
-
-	/* Not should be called for INSERT */
-	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
-		/* internal error */
-		elog(ERROR, "check_foreign_key: cannot process INSERT events");
-
-	if (!TRIGGER_FIRED_AFTER(trigdata->tg_event))
-		/* internal error */
-		elog(ERROR, "check_foreign_key: must be fired by AFTER trigger");
-
-	/* Have to check tg_trigtuple - tuple being deleted */
-	trigtuple = trigdata->tg_trigtuple;
-
-	/*
-	 * But if this is UPDATE then we have to return tg_newtuple. Also, if key
-	 * in tg_newtuple is the same as in tg_trigtuple then nothing to do.
-	 */
-	is_update = 0;
-	if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-	{
-		newtuple = trigdata->tg_newtuple;
-		is_update = 1;
-	}
-	trigger = trigdata->tg_trigger;
-	nargs = trigger->tgnargs;
-	args = trigger->tgargs;
-
-	if (nargs < 5)				/* nrefs, action, key, Relation, key - at
-								 * least */
-		/* internal error */
-		elog(ERROR, "check_foreign_key: too short %d (< 5) list of arguments", nargs);
-
-	nrefs = pg_strtoint32(args[0]);
-	if (nrefs < 1)
-		/* internal error */
-		elog(ERROR, "check_foreign_key: %d (< 1) number of references specified", nrefs);
-	action = pg_ascii_tolower((unsigned char) *(args[1]));
-	if (action != 'r' && action != 'c' && action != 's')
-		/* internal error */
-		elog(ERROR, "check_foreign_key: invalid action %s", args[1]);
-	nargs -= 2;
-	args += 2;
-	nkeys = (nargs - nrefs) / (nrefs + 1);
-	if (nkeys <= 0 || nargs != (nrefs + nkeys * (nrefs + 1)))
-		/* internal error */
-		elog(ERROR, "check_foreign_key: invalid number of arguments %d for %d references",
-			 nargs + 2, nrefs);
-
-	rel = trigdata->tg_relation;
-	tupdesc = rel->rd_att;
-
-	/* Connect to SPI manager */
-	SPI_connect();
-
-	/*
-	 * We use SPI plan preparation feature, so allocate space to place key
-	 * values.
-	 */
-	kvals = (Datum *) palloc(nkeys * sizeof(Datum));
-
-	/* allocate argtypes for preparation */
-	argtypes = (Oid *) palloc(nkeys * sizeof(Oid));
-
-	/* For each column in key ... */
-	for (i = 0; i < nkeys; i++)
-	{
-		/* get index of column in tuple */
-		int			fnumber = SPI_fnumber(tupdesc, args[i]);
-
-		/* Bad guys may give us un-existing column in CREATE TRIGGER */
-		if (fnumber <= 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("there is no attribute \"%s\" in relation \"%s\"",
-							args[i], SPI_getrelname(rel))));
-
-		/* Well, get binary (in internal format) value of column */
-		kvals[i] = SPI_getbinval(trigtuple, tupdesc, fnumber, &isnull);
-
-		/*
-		 * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
-		 * DON'T FORGET return tuple! Executor inserts tuple you're returning!
-		 * If you return NULL then nothing will be inserted!
-		 */
-		if (isnull)
-		{
-			SPI_finish();
-			return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
-		}
-
-		/*
-		 * If UPDATE then get column value from new tuple being inserted and
-		 * compare is this the same as old one. For the moment we use string
-		 * presentation of values...
-		 */
-		if (newtuple != NULL)
-		{
-			char	   *oldval = SPI_getvalue(trigtuple, tupdesc, fnumber);
-			char	   *newval;
-
-			/* this shouldn't happen! SPI_ERROR_NOOUTFUNC ? */
-			if (oldval == NULL)
-				/* internal error */
-				elog(ERROR, "check_foreign_key: SPI_getvalue returned %s", SPI_result_code_string(SPI_result));
-			newval = SPI_getvalue(newtuple, tupdesc, fnumber);
-			if (newval == NULL || strcmp(oldval, newval) != 0)
-				isequal = false;
-		}
-
-		/* Get typeId of column */
-		argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
-	}
-	args_temp = args;
-	nargs -= nkeys;
-	args += nkeys;
-	args2 = args;
-
-	splan = (SPIPlanPtr *) palloc(nrefs * sizeof(SPIPlanPtr));
-
-	for (r = 0; r < nrefs; r++)
-	{
-		StringInfoData sql;
-		SPIPlanPtr	pplan;
-
-		initStringInfo(&sql);
-
-		relname = args2[0];
-
-		/*---------
-		 * For 'R'estrict action we construct SELECT query:
-		 *
-		 *	SELECT 1
-		 *	FROM _referencing_relation_
-		 *	WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]]
-		 *
-		 *	to check is tuple referenced or not.
-		 *---------
-		 */
-		if (action == 'r')
-			appendStringInfo(&sql, "select 1 from %s where ", relname);
-
-		/*---------
-		 * For 'C'ascade action we construct DELETE query
-		 *
-		 *	DELETE
-		 *	FROM _referencing_relation_
-		 *	WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]]
-		 *
-		 * to delete all referencing tuples.
-		 *---------
-		 */
-
-		/*
-		 * Max : Cascade with UPDATE query i create update query that updates
-		 * new key values in referenced tables
-		 */
-
-
-		else if (action == 'c')
-		{
-			if (is_update == 1)
-			{
-				int			fn;
-				char	   *nv;
-				int			k;
-
-				appendStringInfo(&sql, "update %s set ", relname);
-				for (k = 1; k <= nkeys; k++)
-				{
-					fn = SPI_fnumber(tupdesc, args_temp[k - 1]);
-					Assert(fn > 0); /* already checked above */
-					nv = SPI_getvalue(newtuple, tupdesc, fn);
-
-					appendStringInfo(&sql, " %s = %s ",
-									 args2[k],
-									 nv ? quote_literal_cstr(nv) : "NULL");
-					if (k < nkeys)
-						appendStringInfoString(&sql, ", ");
-				}
-				appendStringInfoString(&sql, " where ");
-			}
-			else
-				/* DELETE */
-				appendStringInfo(&sql, "delete from %s where ", relname);
-		}
-
-		/*
-		 * For 'S'etnull action we construct UPDATE query - UPDATE
-		 * _referencing_relation_ SET Fkey1 null [, Fkey2 null [...]] WHERE
-		 * Fkey1 = $1 [AND Fkey2 = $2 [...]] - to set key columns in all
-		 * referencing tuples to NULL.
-		 */
-		else if (action == 's')
-		{
-			appendStringInfo(&sql, "update %s set ", relname);
-			for (i = 1; i <= nkeys; i++)
-			{
-				appendStringInfo(&sql, "%s = null", args2[i]);
-				if (i < nkeys)
-					appendStringInfoString(&sql, ", ");
-			}
-			appendStringInfoString(&sql, " where ");
-		}
-
-		/* Construct WHERE qual */
-		for (i = 1; i <= nkeys; i++)
-		{
-			appendStringInfo(&sql, "%s = $%d ", args2[i], i);
-			if (i < nkeys)
-				appendStringInfoString(&sql, "and ");
-		}
-
-		/* Prepare plan for query */
-		pplan = SPI_prepare(sql.data, nkeys, argtypes);
-		if (pplan == NULL)
-			/* internal error */
-			elog(ERROR, "check_foreign_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result));
-
-		splan[r] = pplan;
-
-		args2 += nkeys + 1;		/* to the next relation */
-
-#ifdef DEBUG_QUERY
-		elog(DEBUG4, "check_foreign_key Debug Query is :  %s ", sql.data);
-#endif
-
-		pfree(sql.data);
-	}
-
-	/*
-	 * If UPDATE and key is not changed ...
-	 */
-	if (newtuple != NULL && isequal)
-	{
-		SPI_finish();
-		return PointerGetDatum(newtuple);
-	}
-
-	/*
-	 * Ok, execute prepared plan(s).
-	 */
-	for (r = 0; r < nrefs; r++)
-	{
-		/*
-		 * For 'R'estrict we may to execute plan for one tuple only, for other
-		 * actions - for all tuples.
-		 */
-		int			tcount = (action == 'r') ? 1 : 0;
-
-		relname = args[0];
-
-		ret = SPI_execp(splan[r], kvals, NULL, tcount);
-		/* we have no NULLs - so we pass   ^^^^  here */
-
-		if (ret < 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
-					 errmsg("SPI_execp returned %d", ret)));
-
-		/* If action is 'R'estrict ... */
-		if (action == 'r')
-		{
-			/* If there is tuple returned by SELECT then ... */
-			if (SPI_processed > 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
-						 errmsg("\"%s\": tuple is referenced in \"%s\"",
-								trigger->tgname, relname)));
-		}
-		else
-		{
-#ifdef REFINT_VERBOSE
-			const char *operation;
-
-			if (action == 'c')
-				operation = is_update ? "updated" : "deleted";
-			else
-				operation = "set to null";
-
-			elog(NOTICE, "%s: " UINT64_FORMAT " tuple(s) of %s are %s",
-				 trigger->tgname, SPI_processed, relname, operation);
-#endif
-		}
-		args += nkeys + 1;		/* to the next relation */
-	}
-
-	SPI_finish();
-
-	return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
-}
diff --git a/contrib/spi/refint.control b/contrib/spi/refint.control
deleted file mode 100644
index cbede45784c..00000000000
--- a/contrib/spi/refint.control
+++ /dev/null
@@ -1,5 +0,0 @@
-# refint extension
-comment = 'functions for implementing referential integrity (obsolete)'
-default_version = '1.0'
-module_pathname = '$libdir/refint'
-relocatable = true
diff --git a/contrib/spi/refint.example b/contrib/spi/refint.example
deleted file mode 100644
index 299166d5041..00000000000
--- a/contrib/spi/refint.example
+++ /dev/null
@@ -1,82 +0,0 @@
---Column ID of table A is primary key:
-
-CREATE TABLE A (
-	ID	int4 not null
-);
-CREATE UNIQUE INDEX AI ON A (ID);
-
---Columns REFB of table B and REFC of C are foreign keys referencing ID of A:
-
-CREATE TABLE B (
-	REFB	int4
-);
-CREATE INDEX BI ON B (REFB);
-
-CREATE TABLE C (
-	REFC	int4
-);
-CREATE INDEX CI ON C (REFC);
-
---Trigger for table A:
-
-CREATE TRIGGER AT BEFORE DELETE OR UPDATE ON A FOR EACH ROW
-EXECUTE PROCEDURE
-check_foreign_key (2, 'cascade', 'ID', 'B', 'REFB', 'C', 'REFC');
-/*
-2	- means that check must be performed for foreign keys of 2 tables.
-cascade	- defines that corresponding keys must be deleted.
-ID	- name of primary key column in triggered table (A). You may
-	  use as many columns as you need.
-B	- name of (first) table with foreign keys.
-REFB	- name of foreign key column in this table. You may use as many
-	  columns as you need, but number of key columns in referenced
-	  table (A) must be the same.
-C	- name of second table with foreign keys.
-REFC	- name of foreign key column in this table.
-*/
-
---Trigger for table B:
-
-CREATE TRIGGER BT BEFORE INSERT OR UPDATE ON B FOR EACH ROW
-EXECUTE PROCEDURE
-check_primary_key ('REFB', 'A', 'ID');
-
-/*
-REFB	- name of foreign key column in triggered (B) table. You may use as
-	  many columns as you need, but number of key columns in referenced
-	  table must be the same.
-A	- referenced table name.
-ID	- name of primary key column in referenced table.
-*/
-
---Trigger for table C:
-
-CREATE TRIGGER CT BEFORE INSERT OR UPDATE ON C FOR EACH ROW
-EXECUTE PROCEDURE
-check_primary_key ('REFC', 'A', 'ID');
-
--- Now try
-
-INSERT INTO A VALUES (10);
-INSERT INTO A VALUES (20);
-INSERT INTO A VALUES (30);
-INSERT INTO A VALUES (40);
-INSERT INTO A VALUES (50);
-
-INSERT INTO B VALUES (1);	-- invalid reference
-INSERT INTO B VALUES (10);
-INSERT INTO B VALUES (30);
-INSERT INTO B VALUES (30);
-
-INSERT INTO C VALUES (11);	-- invalid reference
-INSERT INTO C VALUES (20);
-INSERT INTO C VALUES (20);
-INSERT INTO C VALUES (30);
-
-DELETE FROM A WHERE ID = 10;
-DELETE FROM A WHERE ID = 20;
-DELETE FROM A WHERE ID = 30;
-
-SELECT * FROM A;
-SELECT * FROM B;
-SELECT * FROM C;
diff --git a/contrib/spi/sql/refint.sql b/contrib/spi/sql/refint.sql
deleted file mode 100644
index 63458127917..00000000000
--- a/contrib/spi/sql/refint.sql
+++ /dev/null
@@ -1,97 +0,0 @@
-CREATE EXTENSION refint;
-
-create table pkeys (pkey1 int4 not null, pkey2 text not null);
-create table fkeys (fkey1 int4, fkey2 text, fkey3 int);
-create table fkeys2 (fkey21 int4, fkey22 text, pkey23 int not null);
-
-create index fkeys_i on fkeys (fkey1, fkey2);
-create index fkeys2_i on fkeys2 (fkey21, fkey22);
-create index fkeys2p_i on fkeys2 (pkey23);
-
-insert into pkeys values (10, '1');
-insert into pkeys values (20, '2');
-insert into pkeys values (30, '3');
-insert into pkeys values (40, '4');
-insert into pkeys values (50, '5');
-insert into pkeys values (60, '6');
-create unique index pkeys_i on pkeys (pkey1, pkey2);
-
---
--- For fkeys:
--- 	(fkey1, fkey2)	--> pkeys (pkey1, pkey2)
--- 	(fkey3)		--> fkeys2 (pkey23)
---
-create trigger check_fkeys_pkey_exist
-	after insert or update on fkeys
-	for each row
-	execute function
-	check_primary_key ('fkey1', 'fkey2', 'pkeys', 'pkey1', 'pkey2');
-
-create trigger check_fkeys_pkey2_exist
-	after insert or update on fkeys
-	for each row
-	execute function check_primary_key ('fkey3', 'fkeys2', 'pkey23');
-
---
--- For fkeys2:
--- 	(fkey21, fkey22)	--> pkeys (pkey1, pkey2)
---
-create trigger check_fkeys2_pkey_exist
-	after insert or update on fkeys2
-	for each row
-	execute procedure
-	check_primary_key ('fkey21', 'fkey22', 'pkeys', 'pkey1', 'pkey2');
-
---
--- For pkeys:
--- 	ON DELETE/UPDATE (pkey1, pkey2) CASCADE:
--- 		fkeys (fkey1, fkey2) and fkeys2 (fkey21, fkey22)
---
-create trigger check_pkeys_fkey_cascade
-	after delete or update on pkeys
-	for each row
-	execute procedure
-	check_foreign_key (2, 'cascade', 'pkey1', 'pkey2',
-	'fkeys', 'fkey1', 'fkey2', 'fkeys2', 'fkey21', 'fkey22');
-
---
--- For fkeys2:
--- 	ON DELETE/UPDATE (pkey23) RESTRICT:
--- 		fkeys (fkey3)
---
-create trigger check_fkeys2_fkey_restrict
-	after delete or update on fkeys2
-	for each row
-	execute procedure check_foreign_key (1, 'restrict', 'pkey23', 'fkeys', 'fkey3');
-
-insert into fkeys2 values (10, '1', 1);
-insert into fkeys2 values (30, '3', 2);
-insert into fkeys2 values (40, '4', 5);
-insert into fkeys2 values (50, '5', 3);
--- no key in pkeys
-insert into fkeys2 values (70, '5', 3);
-
-insert into fkeys values (10, '1', 2);
-insert into fkeys values (30, '3', 3);
-insert into fkeys values (40, '4', 2);
-insert into fkeys values (50, '5', 2);
--- no key in pkeys
-insert into fkeys values (70, '5', 1);
--- no key in fkeys2
-insert into fkeys values (60, '6', 4);
-
-delete from pkeys where pkey1 = 30 and pkey2 = '3';
-delete from pkeys where pkey1 = 40 and pkey2 = '4';
-update pkeys set pkey1 = 7, pkey2 = '70' where pkey1 = 50 and pkey2 = '5';
-update pkeys set pkey1 = 7, pkey2 = '70' where pkey1 = 10 and pkey2 = '1';
-
-SELECT trigger_name, event_manipulation, event_object_schema, event_object_table,
-       action_order, action_condition, action_orientation, action_timing,
-       action_reference_old_table, action_reference_new_table
-  FROM information_schema.triggers
-  WHERE event_object_table in ('pkeys', 'fkeys', 'fkeys2')
-  ORDER BY trigger_name COLLATE "C", 2;
-
-DROP TABLE pkeys;
-DROP TABLE fkeys;
-DROP TABLE fkeys2;
diff --git a/doc/src/sgml/contrib-spi.sgml b/doc/src/sgml/contrib-spi.sgml
index 6fa9479d1b9..b585083c6fb 100644
--- a/doc/src/sgml/contrib-spi.sgml
+++ b/doc/src/sgml/contrib-spi.sgml
@@ -24,57 +24,6 @@
   separately-installable extension.
  </para>
 
- <sect2 id="contrib-spi-refint">
-  <title>refint &mdash; Functions for Implementing Referential Integrity</title>
-
-  <para>
-   <function>check_primary_key()</function> and
-   <function>check_foreign_key()</function> are used to check foreign key constraints.
-   (This functionality is long since superseded by the built-in foreign
-   key mechanism, of course, but the module is still useful as an example.)
-  </para>
-
-  <para>
-   <function>check_primary_key()</function> checks the referencing table.
-   To use, create an <literal>AFTER INSERT OR UPDATE</literal> trigger using this
-   function on a table referencing another table. Specify as the trigger
-   arguments: the referencing table's column name(s) which form the foreign
-   key, the referenced table name, and the column names in the referenced table
-   which form the primary/unique key.  To handle multiple foreign
-   keys, create a trigger for each reference.
-  </para>
-
-  <para>
-   <function>check_foreign_key()</function> checks the referenced table.
-   To use, create an <literal>AFTER DELETE OR UPDATE</literal> trigger using this
-   function on a table referenced by other table(s).  Specify as the trigger
-   arguments: the number of referencing tables for which the function has to
-   perform checking, the action if a referencing key is found
-   (<literal>cascade</literal> &mdash; to delete the referencing row,
-   <literal>restrict</literal> &mdash; to abort transaction if referencing keys
-   exist, <literal>setnull</literal> &mdash; to set referencing key fields to null),
-   the triggered table's column names which form the primary/unique key, then
-   the referencing table name and column names (repeated for as many
-   referencing tables as were specified by first argument).  Note that the
-   primary/unique key columns should be marked NOT NULL and should have a
-   unique index.
-  </para>
-
-  <para>
-   Note that if these triggers are executed from
-   another <literal>BEFORE</literal> trigger, they can fail unexpectedly. For
-   example, if a user inserts row1 and then the <literal>BEFORE</literal>
-   trigger inserts row2 and calls a trigger with the
-   <function>check_foreign_key()</function>,
-   the <function>check_foreign_key()</function>
-   function will not see row1 and will fail.
-  </para>
-
-  <para>
-   There are examples in <filename>refint.example</filename>.
-  </para>
- </sect2>
-
  <sect2 id="contrib-spi-autoinc">
   <title>autoinc &mdash; Functions for Autoincrementing Fields</title>
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8cf40c87043..7903f1640c1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -723,7 +723,6 @@ ENGINE
 EOM_flatten_into_method
 EOM_get_flat_size_method
 EPQState
-EPlan
 EState
 EStatus
 EVP_CIPHER
-- 
2.43.0

