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:
parent
c57971034e
commit
b1720fe63f
4
contrib/spi/.gitignore
vendored
Normal file
4
contrib/spi/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Generated subdirectories
|
||||
/log/
|
||||
/results/
|
||||
/tmp_check/
|
@ -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;
|
||||
|
50
contrib/spi/expected/autoinc.out
Normal file
50
contrib/spi/expected/autoinc.out
Normal 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)
|
||||
|
113
contrib/spi/expected/refint.out
Normal file
113
contrib/spi/expected/refint.out
Normal 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;
|
@ -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',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
33
contrib/spi/sql/autoinc.sql
Normal file
33
contrib/spi/sql/autoinc.sql
Normal 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;
|
97
contrib/spi/sql/refint.sql
Normal file
97
contrib/spi/sql/refint.sql
Normal 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;
|
@ -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>
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
--
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
--
|
||||
|
Loading…
x
Reference in New Issue
Block a user