From 380e431166a1f87c5bd6e651049e63e2e8334e6f Mon Sep 17 00:00:00 2001 From: Ayush Tiwari 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 - -#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. - - refint — Functions for Implementing Referential Integrity - - - check_primary_key() and - check_foreign_key() 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.) - - - - check_primary_key() checks the referencing table. - To use, create an AFTER INSERT OR UPDATE 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. - - - - check_foreign_key() checks the referenced table. - To use, create an AFTER DELETE OR UPDATE 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 - (cascade — to delete the referencing row, - restrict — to abort transaction if referencing keys - exist, setnull — 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. - - - - Note that if these triggers are executed from - another BEFORE trigger, they can fail unexpectedly. For - example, if a user inserts row1 and then the BEFORE - trigger inserts row2 and calls a trigger with the - check_foreign_key(), - the check_foreign_key() - function will not see row1 and will fail. - - - - There are examples in refint.example. - - - autoinc — Functions for Autoincrementing Fields 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