1
0
mirror of https://github.com/postgres/postgres.git synced 2025-04-17 02:23:15 +03:00

Move contrib/spi testing from core regression tests to contrib/spi.

It's weird to have the core regression tests depending on contrib
code, and coverage testing shows that those test queries add nothing
to the core-code coverage of the core tests.  So pull those test bits
out and put them into ordinary test scripts inside contrib/spi/,
making that more like other contrib modules.

Aside from being structurally nicer, anything we can take out of the
core tests (which are executed multiple times per check-world run)
and put into tests executed only once should be a win.  It doesn't
look like this change will buy a whole lot of milliseconds, but a
cycle saved is a cycle earned.

Also, there is some discussion around possibly removing refint and/or
autoinc altogether.  I don't know if that will happen, but we'd
certainly need to decouple them from the core tests to do so.

The tests for autoinc were quite intertwined with the undocumented
"ttdummy" trigger in regress.c.  That made the tests very hard to
understand and contributed nothing to autoinc's testing either.
So I just deleted ttdummy and rewrote the autoinc tests without it.

I realized while doing this that the description of autoinc in
the SGML docs is not a great description of what the function
actually does, so the patch includes some updates to those docs.

Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Heikki Linnakangas <hlinnaka@iki.fi>
Discussion: https://postgr.es/m/3872677.1744077559@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2025-04-08 19:12:03 -04:00
parent c57971034e
commit b1720fe63f
17 changed files with 332 additions and 736 deletions

4
contrib/spi/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Generated subdirectories
/log/
/results/
/tmp_check/

View File

@ -10,6 +10,8 @@ DATA = autoinc--1.0.sql \
refint--1.0.sql
PGFILEDESC = "spi - examples of using SPI and triggers"
REGRESS = autoinc refint
DOCS = $(addsuffix .example, $(MODULES))
# this is needed for the regression tests;

View File

@ -0,0 +1,50 @@
CREATE EXTENSION autoinc;
create sequence aitest_seq increment 10 start 0 minvalue 0;
create table aitest (
price_id int4,
price_val int4,
price_on int4
);
create trigger aiserial
before insert or update on aitest
for each row
execute procedure
autoinc (price_on, aitest_seq);
insert into aitest values (1, 1, null);
insert into aitest values (2, 2, 0);
insert into aitest values (3, 3, 1);
select * from aitest;
price_id | price_val | price_on
----------+-----------+----------
1 | 1 | 10
2 | 2 | 20
3 | 3 | 1
(3 rows)
update aitest set price_on = 11;
select * from aitest;
price_id | price_val | price_on
----------+-----------+----------
1 | 1 | 11
2 | 2 | 11
3 | 3 | 11
(3 rows)
update aitest set price_on = 0;
select * from aitest;
price_id | price_val | price_on
----------+-----------+----------
1 | 1 | 30
2 | 2 | 40
3 | 3 | 50
(3 rows)
update aitest set price_on = null;
select * from aitest;
price_id | price_val | price_on
----------+-----------+----------
1 | 1 | 60
2 | 2 | 70
3 | 3 | 80
(3 rows)

View File

@ -0,0 +1,113 @@
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;

View File

@ -107,3 +107,15 @@ install_data('refint.control', 'refint--1.0.sql',
install_data('refint.example',
kwargs: contrib_doc_args,
)
tests += {
'name': 'spi',
'sd': meson.current_source_dir(),
'bd': meson.current_build_dir(),
'regress': {
'sql': [
'autoinc',
'refint',
],
},
}

View File

@ -0,0 +1,33 @@
CREATE EXTENSION autoinc;
create sequence aitest_seq increment 10 start 0 minvalue 0;
create table aitest (
price_id int4,
price_val int4,
price_on int4
);
create trigger aiserial
before insert or update on aitest
for each row
execute procedure
autoinc (price_on, aitest_seq);
insert into aitest values (1, 1, null);
insert into aitest values (2, 2, 0);
insert into aitest values (3, 3, 1);
select * from aitest;
update aitest set price_on = 11;
select * from aitest;
update aitest set price_on = 0;
select * from aitest;
update aitest set price_on = null;
select * from aitest;

View File

@ -0,0 +1,97 @@
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;

View File

@ -81,10 +81,12 @@
<para>
<function>autoinc()</function> is a trigger that stores the next value of
a sequence into an integer field. This has some overlap with the
built-in <quote>serial column</quote> feature, but it is not the same:
<function>autoinc()</function> will override attempts to substitute a
different field value during inserts, and optionally it can be
used to increment the field during updates, too.
built-in <quote>serial column</quote> feature, but it is not the same.
The trigger will replace the field's value only if that value is
initially zero or null (after the action of the SQL statement that
inserted or updated the row). Also, if the sequence's next value is
zero, <function>nextval()</function> will be called a second time in
order to obtain a non-zero value.
</para>
<para>

View File

@ -87,26 +87,6 @@ installdirs-tests: installdirs
$(MKDIR_P) $(patsubst $(srcdir)/%/,'$(DESTDIR)$(pkglibdir)/regress/%',$(sort $(dir $(regress_data_files))))
# Get some extra C modules from contrib/spi
all: refint$(DLSUFFIX) autoinc$(DLSUFFIX)
refint$(DLSUFFIX): $(top_builddir)/contrib/spi/refint$(DLSUFFIX)
cp $< $@
autoinc$(DLSUFFIX): $(top_builddir)/contrib/spi/autoinc$(DLSUFFIX)
cp $< $@
$(top_builddir)/contrib/spi/refint$(DLSUFFIX): | submake-contrib-spi ;
$(top_builddir)/contrib/spi/autoinc$(DLSUFFIX): | submake-contrib-spi ;
submake-contrib-spi: | submake-libpgport submake-generated-headers
$(MAKE) -C $(top_builddir)/contrib/spi
.PHONY: submake-contrib-spi
##
## Run tests
##
@ -148,7 +128,7 @@ bigcheck: all | temp-install
clean distclean: clean-lib
# things built by `all' target
rm -f $(OBJS) refint$(DLSUFFIX) autoinc$(DLSUFFIX)
rm -f $(OBJS)
rm -f pg_regress_main.o pg_regress.o pg_regress$(X)
# things created by various check targets
rm -rf $(pg_regress_clean_files)

View File

@ -2898,6 +2898,8 @@ select * from my_locks order by 1;
rollback;
begin;
create function ttdummy () returns trigger language plpgsql as
$$ begin return new; end $$;
create trigger ttdummy
before delete or update on alterlock
for each row

View File

@ -205,10 +205,6 @@ CREATE FUNCTION binary_coercible(oid, oid)
RETURNS bool
AS :'regresslib', 'binary_coercible'
LANGUAGE C STRICT STABLE PARALLEL SAFE;
CREATE FUNCTION ttdummy ()
RETURNS trigger
AS :'regresslib'
LANGUAGE C;
-- Use hand-rolled hash functions and operator classes to get predictable
-- result on different machines. The hash function for int4 simply returns
-- the sum of the values passed to it and the one for text returns the length

View File

@ -4,146 +4,11 @@
-- directory paths and dlsuffix are passed to us in environment variables
\getenv libdir PG_LIBDIR
\getenv dlsuffix PG_DLSUFFIX
\set autoinclib :libdir '/autoinc' :dlsuffix
\set refintlib :libdir '/refint' :dlsuffix
\set regresslib :libdir '/regress' :dlsuffix
CREATE FUNCTION autoinc ()
RETURNS trigger
AS :'autoinclib'
LANGUAGE C;
CREATE FUNCTION check_primary_key ()
RETURNS trigger
AS :'refintlib'
LANGUAGE C;
CREATE FUNCTION check_foreign_key ()
RETURNS trigger
AS :'refintlib'
LANGUAGE C;
CREATE FUNCTION trigger_return_old ()
RETURNS trigger
AS :'regresslib'
LANGUAGE C;
CREATE FUNCTION set_ttdummy (int4)
RETURNS int4
AS :'regresslib'
LANGUAGE C STRICT;
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');
-- Test comments
COMMENT ON TRIGGER check_fkeys2_pkey_bad ON fkeys2 IS 'wrong';
ERROR: trigger "check_fkeys2_pkey_bad" for table "fkeys2" does not exist
COMMENT ON TRIGGER check_fkeys2_pkey_exist ON fkeys2 IS 'right';
COMMENT ON TRIGGER check_fkeys2_pkey_exist ON fkeys2 IS NULL;
--
-- 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;
-- Check behavior when trigger returns unmodified trigtuple
create table trigtest (f1 int, f2 text);
create trigger trigger_return_old
@ -294,143 +159,6 @@ select * from trigtest;
(1 row)
drop table trigtest;
create sequence ttdummy_seq increment 10 start 0 minvalue 0;
create table tttest (
price_id int4,
price_val int4,
price_on int4,
price_off int4 default 999999
);
create trigger ttdummy
before delete or update on tttest
for each row
execute procedure
ttdummy (price_on, price_off);
create trigger ttserial
before insert or update on tttest
for each row
execute procedure
autoinc (price_on, ttdummy_seq);
insert into tttest values (1, 1, null);
insert into tttest values (2, 2, null);
insert into tttest values (3, 3, 0);
select * from tttest;
price_id | price_val | price_on | price_off
----------+-----------+----------+-----------
1 | 1 | 10 | 999999
2 | 2 | 20 | 999999
3 | 3 | 30 | 999999
(3 rows)
delete from tttest where price_id = 2;
select * from tttest;
price_id | price_val | price_on | price_off
----------+-----------+----------+-----------
1 | 1 | 10 | 999999
3 | 3 | 30 | 999999
2 | 2 | 20 | 40
(3 rows)
-- what do we see ?
-- get current prices
select * from tttest where price_off = 999999;
price_id | price_val | price_on | price_off
----------+-----------+----------+-----------
1 | 1 | 10 | 999999
3 | 3 | 30 | 999999
(2 rows)
-- change price for price_id == 3
update tttest set price_val = 30 where price_id = 3;
select * from tttest;
price_id | price_val | price_on | price_off
----------+-----------+----------+-----------
1 | 1 | 10 | 999999
2 | 2 | 20 | 40
3 | 30 | 50 | 999999
3 | 3 | 30 | 50
(4 rows)
-- now we want to change pric_id in ALL tuples
-- this gets us not what we need
update tttest set price_id = 5 where price_id = 3;
select * from tttest;
price_id | price_val | price_on | price_off
----------+-----------+----------+-----------
1 | 1 | 10 | 999999
2 | 2 | 20 | 40
3 | 3 | 30 | 50
5 | 30 | 60 | 999999
3 | 30 | 50 | 60
(5 rows)
-- restore data as before last update:
select set_ttdummy(0);
set_ttdummy
-------------
1
(1 row)
delete from tttest where price_id = 5;
update tttest set price_off = 999999 where price_val = 30;
select * from tttest;
price_id | price_val | price_on | price_off
----------+-----------+----------+-----------
1 | 1 | 10 | 999999
2 | 2 | 20 | 40
3 | 3 | 30 | 50
3 | 30 | 50 | 999999
(4 rows)
-- and try change price_id now!
update tttest set price_id = 5 where price_id = 3;
select * from tttest;
price_id | price_val | price_on | price_off
----------+-----------+----------+-----------
1 | 1 | 10 | 999999
2 | 2 | 20 | 40
5 | 3 | 30 | 50
5 | 30 | 50 | 999999
(4 rows)
-- isn't it what we need ?
select set_ttdummy(1);
set_ttdummy
-------------
0
(1 row)
-- we want to correct some "date"
update tttest set price_on = -1 where price_id = 1;
ERROR: ttdummy (tttest): you cannot change price_on and/or price_off columns (use set_ttdummy)
-- but this doesn't work
-- try in this way
select set_ttdummy(0);
set_ttdummy
-------------
1
(1 row)
update tttest set price_on = -1 where price_id = 1;
select * from tttest;
price_id | price_val | price_on | price_off
----------+-----------+----------+-----------
2 | 2 | 20 | 40
5 | 3 | 30 | 50
5 | 30 | 50 | 999999
1 | 1 | -1 | 999999
(4 rows)
-- isn't it what we need ?
-- get price for price_id == 5 as it was @ "date" 35
select * from tttest where price_on <= 35 and price_off > 35 and price_id = 5;
price_id | price_val | price_on | price_off
----------+-----------+----------+-----------
5 | 3 | 30 | 50
(1 row)
drop table tttest;
drop sequence ttdummy_seq;
--
-- tests for per-statement triggers
--
@ -493,6 +221,11 @@ SELECT * FROM main_table ORDER BY a, b;
|
(8 rows)
-- Test comments
COMMENT ON TRIGGER no_such_trigger ON main_table IS 'wrong';
ERROR: trigger "no_such_trigger" for table "main_table" does not exist
COMMENT ON TRIGGER before_ins_stmt_trig ON main_table IS 'right';
COMMENT ON TRIGGER before_ins_stmt_trig ON main_table IS NULL;
--
-- test triggers with WHEN clause
--

View File

@ -43,23 +43,6 @@ regress_module = shared_module('regress',
)
test_install_libs += regress_module
# Get some extra C modules from contrib/spi but mark them as not to be
# installed.
# FIXME: avoid the duplication.
autoinc_regress = shared_module('autoinc',
['../../../contrib/spi/autoinc.c'],
kwargs: pg_test_mod_args,
)
test_install_libs += autoinc_regress
refint_regress = shared_module('refint',
['../../../contrib/spi/refint.c'],
c_args: refint_cflags,
kwargs: pg_test_mod_args,
)
test_install_libs += refint_regress
tests += {
'name': 'regress',

View File

@ -266,226 +266,6 @@ trigger_return_old(PG_FUNCTION_ARGS)
return PointerGetDatum(tuple);
}
#define TTDUMMY_INFINITY 999999
static SPIPlanPtr splan = NULL;
static bool ttoff = false;
PG_FUNCTION_INFO_V1(ttdummy);
Datum
ttdummy(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
Trigger *trigger; /* to get trigger name */
char **args; /* arguments */
int attnum[2]; /* fnumbers of start/stop columns */
Datum oldon,
oldoff;
Datum newon,
newoff;
Datum *cvals; /* column values */
char *cnulls; /* column nulls */
char *relname; /* triggered relation name */
Relation rel; /* triggered relation */
HeapTuple trigtuple;
HeapTuple newtuple = NULL;
HeapTuple rettuple;
TupleDesc tupdesc; /* tuple description */
int natts; /* # of attributes */
bool isnull; /* to know is some column NULL or not */
int ret;
int i;
if (!CALLED_AS_TRIGGER(fcinfo))
elog(ERROR, "ttdummy: not fired by trigger manager");
if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
elog(ERROR, "ttdummy: must be fired for row");
if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
elog(ERROR, "ttdummy: must be fired before event");
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
elog(ERROR, "ttdummy: cannot process INSERT event");
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
newtuple = trigdata->tg_newtuple;
trigtuple = trigdata->tg_trigtuple;
rel = trigdata->tg_relation;
relname = SPI_getrelname(rel);
/* check if TT is OFF for this relation */
if (ttoff) /* OFF - nothing to do */
{
pfree(relname);
return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple);
}
trigger = trigdata->tg_trigger;
if (trigger->tgnargs != 2)
elog(ERROR, "ttdummy (%s): invalid (!= 2) number of arguments %d",
relname, trigger->tgnargs);
args = trigger->tgargs;
tupdesc = rel->rd_att;
natts = tupdesc->natts;
for (i = 0; i < 2; i++)
{
attnum[i] = SPI_fnumber(tupdesc, args[i]);
if (attnum[i] <= 0)
elog(ERROR, "ttdummy (%s): there is no attribute %s",
relname, args[i]);
if (SPI_gettypeid(tupdesc, attnum[i]) != INT4OID)
elog(ERROR, "ttdummy (%s): attribute %s must be of integer type",
relname, args[i]);
}
oldon = SPI_getbinval(trigtuple, tupdesc, attnum[0], &isnull);
if (isnull)
elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[0]);
oldoff = SPI_getbinval(trigtuple, tupdesc, attnum[1], &isnull);
if (isnull)
elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[1]);
if (newtuple != NULL) /* UPDATE */
{
newon = SPI_getbinval(newtuple, tupdesc, attnum[0], &isnull);
if (isnull)
elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[0]);
newoff = SPI_getbinval(newtuple, tupdesc, attnum[1], &isnull);
if (isnull)
elog(ERROR, "ttdummy (%s): %s must be NOT NULL", relname, args[1]);
if (oldon != newon || oldoff != newoff)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ttdummy (%s): you cannot change %s and/or %s columns (use set_ttdummy)",
relname, args[0], args[1])));
if (newoff != TTDUMMY_INFINITY)
{
pfree(relname); /* allocated in upper executor context */
return PointerGetDatum(NULL);
}
}
else if (oldoff != TTDUMMY_INFINITY) /* DELETE */
{
pfree(relname);
return PointerGetDatum(NULL);
}
newoff = DirectFunctionCall1(nextval, CStringGetTextDatum("ttdummy_seq"));
/* nextval now returns int64; coerce down to int32 */
newoff = Int32GetDatum((int32) DatumGetInt64(newoff));
/* Connect to SPI manager */
SPI_connect();
/* Fetch tuple values and nulls */
cvals = (Datum *) palloc(natts * sizeof(Datum));
cnulls = (char *) palloc(natts * sizeof(char));
for (i = 0; i < natts; i++)
{
cvals[i] = SPI_getbinval((newtuple != NULL) ? newtuple : trigtuple,
tupdesc, i + 1, &isnull);
cnulls[i] = (isnull) ? 'n' : ' ';
}
/* change date column(s) */
if (newtuple) /* UPDATE */
{
cvals[attnum[0] - 1] = newoff; /* start_date eq current date */
cnulls[attnum[0] - 1] = ' ';
cvals[attnum[1] - 1] = TTDUMMY_INFINITY; /* stop_date eq INFINITY */
cnulls[attnum[1] - 1] = ' ';
}
else
/* DELETE */
{
cvals[attnum[1] - 1] = newoff; /* stop_date eq current date */
cnulls[attnum[1] - 1] = ' ';
}
/* if there is no plan ... */
if (splan == NULL)
{
SPIPlanPtr pplan;
Oid *ctypes;
char *query;
/* allocate space in preparation */
ctypes = (Oid *) palloc(natts * sizeof(Oid));
query = (char *) palloc(100 + 16 * natts);
/*
* Construct query: INSERT INTO _relation_ VALUES ($1, ...)
*/
sprintf(query, "INSERT INTO %s VALUES (", relname);
for (i = 1; i <= natts; i++)
{
sprintf(query + strlen(query), "$%d%s",
i, (i < natts) ? ", " : ")");
ctypes[i - 1] = SPI_gettypeid(tupdesc, i);
}
/* Prepare plan for query */
pplan = SPI_prepare(query, natts, ctypes);
if (pplan == NULL)
elog(ERROR, "ttdummy (%s): SPI_prepare returned %s", relname, SPI_result_code_string(SPI_result));
if (SPI_keepplan(pplan))
elog(ERROR, "ttdummy (%s): SPI_keepplan failed", relname);
splan = pplan;
}
ret = SPI_execp(splan, cvals, cnulls, 0);
if (ret < 0)
elog(ERROR, "ttdummy (%s): SPI_execp returned %d", relname, ret);
/* Tuple to return to upper Executor ... */
if (newtuple) /* UPDATE */
rettuple = SPI_modifytuple(rel, trigtuple, 1, &(attnum[1]), &newoff, NULL);
else /* DELETE */
rettuple = trigtuple;
SPI_finish(); /* don't forget say Bye to SPI mgr */
pfree(relname);
return PointerGetDatum(rettuple);
}
PG_FUNCTION_INFO_V1(set_ttdummy);
Datum
set_ttdummy(PG_FUNCTION_ARGS)
{
int32 on = PG_GETARG_INT32(0);
if (ttoff) /* OFF currently */
{
if (on == 0)
PG_RETURN_INT32(0);
/* turn ON */
ttoff = false;
PG_RETURN_INT32(0);
}
/* ON currently */
if (on != 0)
PG_RETURN_INT32(1);
/* turn OFF */
ttoff = true;
PG_RETURN_INT32(1);
}
/*
* Type int44 has no real-world use, but the regression tests use it

View File

@ -1810,6 +1810,8 @@ select * from my_locks order by 1;
rollback;
begin;
create function ttdummy () returns trigger language plpgsql as
$$ begin return new; end $$;
create trigger ttdummy
before delete or update on alterlock
for each row

View File

@ -252,11 +252,6 @@ CREATE FUNCTION binary_coercible(oid, oid)
AS :'regresslib', 'binary_coercible'
LANGUAGE C STRICT STABLE PARALLEL SAFE;
CREATE FUNCTION ttdummy ()
RETURNS trigger
AS :'regresslib'
LANGUAGE C;
-- Use hand-rolled hash functions and operator classes to get predictable
-- result on different machines. The hash function for int4 simply returns
-- the sum of the values passed to it and the one for text returns the length

View File

@ -6,135 +6,13 @@
\getenv libdir PG_LIBDIR
\getenv dlsuffix PG_DLSUFFIX
\set autoinclib :libdir '/autoinc' :dlsuffix
\set refintlib :libdir '/refint' :dlsuffix
\set regresslib :libdir '/regress' :dlsuffix
CREATE FUNCTION autoinc ()
RETURNS trigger
AS :'autoinclib'
LANGUAGE C;
CREATE FUNCTION check_primary_key ()
RETURNS trigger
AS :'refintlib'
LANGUAGE C;
CREATE FUNCTION check_foreign_key ()
RETURNS trigger
AS :'refintlib'
LANGUAGE C;
CREATE FUNCTION trigger_return_old ()
RETURNS trigger
AS :'regresslib'
LANGUAGE C;
CREATE FUNCTION set_ttdummy (int4)
RETURNS int4
AS :'regresslib'
LANGUAGE C STRICT;
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');
-- Test comments
COMMENT ON TRIGGER check_fkeys2_pkey_bad ON fkeys2 IS 'wrong';
COMMENT ON TRIGGER check_fkeys2_pkey_exist ON fkeys2 IS 'right';
COMMENT ON TRIGGER check_fkeys2_pkey_exist ON fkeys2 IS NULL;
--
-- 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;
-- Check behavior when trigger returns unmodified trigtuple
create table trigtest (f1 int, f2 text);
@ -214,77 +92,6 @@ select * from trigtest;
drop table trigtest;
create sequence ttdummy_seq increment 10 start 0 minvalue 0;
create table tttest (
price_id int4,
price_val int4,
price_on int4,
price_off int4 default 999999
);
create trigger ttdummy
before delete or update on tttest
for each row
execute procedure
ttdummy (price_on, price_off);
create trigger ttserial
before insert or update on tttest
for each row
execute procedure
autoinc (price_on, ttdummy_seq);
insert into tttest values (1, 1, null);
insert into tttest values (2, 2, null);
insert into tttest values (3, 3, 0);
select * from tttest;
delete from tttest where price_id = 2;
select * from tttest;
-- what do we see ?
-- get current prices
select * from tttest where price_off = 999999;
-- change price for price_id == 3
update tttest set price_val = 30 where price_id = 3;
select * from tttest;
-- now we want to change pric_id in ALL tuples
-- this gets us not what we need
update tttest set price_id = 5 where price_id = 3;
select * from tttest;
-- restore data as before last update:
select set_ttdummy(0);
delete from tttest where price_id = 5;
update tttest set price_off = 999999 where price_val = 30;
select * from tttest;
-- and try change price_id now!
update tttest set price_id = 5 where price_id = 3;
select * from tttest;
-- isn't it what we need ?
select set_ttdummy(1);
-- we want to correct some "date"
update tttest set price_on = -1 where price_id = 1;
-- but this doesn't work
-- try in this way
select set_ttdummy(0);
update tttest set price_on = -1 where price_id = 1;
select * from tttest;
-- isn't it what we need ?
-- get price for price_id == 5 as it was @ "date" 35
select * from tttest where price_on <= 35 and price_off > 35 and price_id = 5;
drop table tttest;
drop sequence ttdummy_seq;
--
-- tests for per-statement triggers
--
@ -346,6 +153,11 @@ COPY main_table (a, b) FROM stdin;
SELECT * FROM main_table ORDER BY a, b;
-- Test comments
COMMENT ON TRIGGER no_such_trigger ON main_table IS 'wrong';
COMMENT ON TRIGGER before_ins_stmt_trig ON main_table IS 'right';
COMMENT ON TRIGGER before_ins_stmt_trig ON main_table IS NULL;
--
-- test triggers with WHEN clause
--