mirror of
https://github.com/postgres/postgres.git
synced 2026-07-09 17:51:00 -04:00
Remove the refint extension.
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 (see commitsb0b6196386,8cfbdf8f4d,260e97733b,611756948e,1fbe2066dc, and1541d91d1c) made it clear that the code has more issues than its sample-code value justifies. Author: Ayush Tiwari <ayushtiwari.slg01@gmail.com> Reviewed-by: solai v <solai.cdac@gmail.com> Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de> Reviewed-by: Daniel Gustafsson <daniel@yesql.se> Reviewed-by: Fujii Masao <masao.fujii@gmail.com> Discussion: https://postgr.es/m/CAJTYsWUHq8Ohc6-N-xamOPYz-q3qUYMtwQX-1%3DZi%3D5N1Q_GSEQ%40mail.gmail.com
This commit is contained in:
parent
8e0c252753
commit
5e90e0914c
13 changed files with 45 additions and 995 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# refint extension
|
||||
comment = 'functions for implementing referential integrity (obsolete)'
|
||||
default_version = '1.0'
|
||||
module_pathname = '$libdir/refint'
|
||||
relocatable = true
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
24
doc/src/sgml/appendix-obsolete-refint.sgml
Normal file
24
doc/src/sgml/appendix-obsolete-refint.sgml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<!-- doc/src/sgml/appendix-obsolete-refint.sgml -->
|
||||
<!--
|
||||
See doc/src/sgml/appendix-obsolete.sgml for why this file exists. Do not change the id attribute.
|
||||
-->
|
||||
|
||||
<sect1 id="contrib-spi-refint">
|
||||
<title>refint Extension Removed</title>
|
||||
|
||||
<indexterm zone="contrib-spi-refint">
|
||||
<primary>refint</primary>
|
||||
</indexterm>
|
||||
|
||||
<para>
|
||||
<productname>PostgreSQL</productname> 19 and below shipped an extension
|
||||
named <literal>refint</literal> (part of the <literal>spi</literal> contrib
|
||||
module) that provided the trigger functions
|
||||
<function>check_primary_key</function> and
|
||||
<function>check_foreign_key</function> as an early way to enforce
|
||||
referential integrity. This functionality was long superseded by the
|
||||
built-in foreign key mechanism (see <xref linkend="ddl-constraints-fk"/>),
|
||||
and the extension was removed in <productname>PostgreSQL</productname> 20.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
|
@ -39,5 +39,6 @@
|
|||
&obsolete-pgresetxlog;
|
||||
&obsolete-pgreceivexlog;
|
||||
&obsolete-auth-radius;
|
||||
&obsolete-refint;
|
||||
|
||||
</appendix>
|
||||
|
|
|
|||
|
|
@ -24,113 +24,6 @@
|
|||
separately-installable extension.
|
||||
</para>
|
||||
|
||||
<sect2 id="contrib-spi-refint">
|
||||
<title>refint — 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>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
<filename>refint</filename> requires a
|
||||
<link linkend="ddl-schemas-patterns">secure schema usage pattern</link> and
|
||||
data types where the equality operator is named <literal>=</literal>.
|
||||
</para>
|
||||
</note>
|
||||
|
||||
<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>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
The <emphasis>referenced</emphasis> table name and column name arguments to
|
||||
<function>check_primary_key()</function> are copied as-is into internally
|
||||
generated SQL statements and therefore must be double-quoted by the user as
|
||||
necessary in the <command>CREATE TRIGGER</command> command. See
|
||||
<xref linkend="sql-syntax-identifiers"/> for more information about quoting
|
||||
SQL identifiers. Conversely, the <emphasis>referencing</emphasis> table
|
||||
column name arguments should not be double quoted. See the following mock
|
||||
example of proper use of <function>check_primary_key()</function>:
|
||||
<programlisting>
|
||||
CREATE TRIGGER mytrigger
|
||||
AFTER INSERT OR UPDATE ON referencing_table
|
||||
FOR EACH ROW EXECUTE PROCEDURE
|
||||
check_primary_key (
|
||||
'column A', 'column B', -- referencing table columns
|
||||
'myschema."referenced table"', -- referenced table
|
||||
'"column A"', '"column B"' -- referenced table columns
|
||||
);
|
||||
</programlisting>
|
||||
</para>
|
||||
</note>
|
||||
|
||||
<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> — to delete the referencing row,
|
||||
<literal>restrict</literal> — to abort transaction if referencing keys
|
||||
exist, <literal>setnull</literal> — to set referencing key fields to null),
|
||||
the referenced 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>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
The <emphasis>referencing</emphasis> table name and column name arguments
|
||||
to <function>check_foreign_key()</function> are copied as-is into
|
||||
internally generated SQL statements and therefore must be double-quoted by
|
||||
the user as necessary in the <command>CREATE TRIGGER</command> command.
|
||||
See <xref linkend="sql-syntax-identifiers"/> for more information about
|
||||
quoting SQL identifiers. Conversely, the <emphasis>referenced</emphasis>
|
||||
table column name arguments should not be double quoted. See the following
|
||||
mock example of proper use of <function>check_foreign_key()</function>:
|
||||
<programlisting>
|
||||
CREATE TRIGGER mytrigger
|
||||
AFTER DELETE OR UPDATE ON referenced_table
|
||||
FOR EACH ROW EXECUTE PROCEDURE
|
||||
check_foreign_key (
|
||||
1, -- number of referencing tables
|
||||
'cascade', -- action
|
||||
'column A', 'column B', -- referenced table columns
|
||||
'myschema."referencing table"', -- referencing table
|
||||
'"column A"', '"column B"' -- referencing table columns
|
||||
);
|
||||
</programlisting>
|
||||
</para>
|
||||
</note>
|
||||
|
||||
<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 — Functions for Autoincrementing Fields</title>
|
||||
|
||||
|
|
|
|||
|
|
@ -209,3 +209,4 @@
|
|||
<!ENTITY obsolete-pgresetxlog SYSTEM "appendix-obsolete-pgresetxlog.sgml">
|
||||
<!ENTITY obsolete-pgreceivexlog SYSTEM "appendix-obsolete-pgreceivexlog.sgml">
|
||||
<!ENTITY obsolete-auth-radius SYSTEM "appendix-obsolete-auth-radius.sgml">
|
||||
<!ENTITY obsolete-refint SYSTEM "appendix-obsolete-refint.sgml">
|
||||
|
|
|
|||
|
|
@ -112,6 +112,21 @@ sub adjust_database_contents
|
|||
'drop extension if exists test_ext7');
|
||||
}
|
||||
|
||||
# refint was removed in v20
|
||||
if ($old_version < 20)
|
||||
{
|
||||
if ($dbnames{"contrib_regression_autoinc"})
|
||||
{
|
||||
_add_st($result, 'contrib_regression_autoinc',
|
||||
'drop extension if exists refint cascade');
|
||||
}
|
||||
if ($dbnames{"regression_spi"})
|
||||
{
|
||||
_add_st($result, 'regression_spi',
|
||||
'drop extension if exists refint cascade');
|
||||
}
|
||||
}
|
||||
|
||||
# btree_gist inet/cidr indexes cannot be upgraded to v19
|
||||
if ($old_version < 19)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue