mirror of
https://github.com/postgres/postgres.git
synced 2025-07-24 14:22:24 +03:00
I send you a attach of my modified refint.c that
works with a new policy in cascade mode . Please Read README.MAX . I do not know if you are the author of refint.c , but if not please tell me who is . Thank you ( excuse me for my bad english) . Massimo Lambertini massimo.lambertini@everex.it
This commit is contained in:
@ -3,34 +3,23 @@ SRCDIR= ../../src
|
|||||||
|
|
||||||
include $(SRCDIR)/Makefile.global
|
include $(SRCDIR)/Makefile.global
|
||||||
|
|
||||||
CONTRIBDIR=$(LIBDIR)/modules
|
|
||||||
|
|
||||||
CFLAGS+= $(CFLAGS_SL) -I$(SRCDIR)/include
|
CFLAGS+= $(CFLAGS_SL) -I$(SRCDIR)/include
|
||||||
|
|
||||||
ifdef REFINT_VERBOSE
|
ifdef REFINT_VERBOSE
|
||||||
CFLAGS+= -DREFINT_VERBOSE
|
CFLAGS+= -DREFINT_VERBOSE
|
||||||
endif
|
endif
|
||||||
|
|
||||||
TARGETS= refint$(DLSUFFIX) refint.sql timetravel$(DLSUFFIX) timetravel.sql \
|
TARGETS= refint$(DLSUFFIX) refint.sql
|
||||||
autoinc$(DLSUFFIX) autoinc.sql moddatetime$(DLSUFFIX) moddatetime.sql \
|
|
||||||
insert_username$(DLSUFFIX) insert_username.sql
|
|
||||||
|
|
||||||
CLEANFILES+= $(TARGETS)
|
CLEANFILES+= $(TARGETS)
|
||||||
|
|
||||||
all:: $(TARGETS)
|
all:: $(TARGETS)
|
||||||
|
|
||||||
install:: all $(CONTRIBDIR)
|
|
||||||
$(INSTALL) -c README $(CONTRIBDIR)/README.spi
|
|
||||||
for f in *.example *.sql *$(DLSUFFIX); do $(INSTALL) -c $$f $(CONTRIBDIR)/$$f; done
|
|
||||||
|
|
||||||
$(CONTRIBDIR):
|
|
||||||
mkdir -p $(CONTRIBDIR)
|
|
||||||
|
|
||||||
%.sql: %.source
|
%.sql: %.source
|
||||||
rm -f $@; \
|
rm -f $@; \
|
||||||
C=`pwd`; \
|
C=`pwd`; \
|
||||||
sed -e "s:_OBJWD_:$(CONTRIBDIR):g" \
|
sed -e "s:_OBJWD_:$$C:g" \
|
||||||
-e "s:_DLSUFFIX_:$(DLSUFFIX):g" < $< > $@
|
-e "s:_DLSUFFIX_:$(DLSUFFIX):g" < $< > $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(TARGETS) *.o
|
rm -f $(TARGETS)
|
||||||
|
@ -8,8 +8,8 @@ table/field names (as described below) while creating a trigger.
|
|||||||
|
|
||||||
check_primary_key () is to used for foreign keys of a table.
|
check_primary_key () is to used for foreign keys of a table.
|
||||||
|
|
||||||
You have to create trigger (BEFORE INSERT OR UPDATE) using this
|
You are to create trigger (BEFORE INSERT OR UPDATE) using this
|
||||||
function on a table referencing another table. You have to specify
|
function on a table referencing another table. You are to specify
|
||||||
as function arguments: triggered table column names which correspond
|
as function arguments: triggered table column names which correspond
|
||||||
to foreign key, referenced table name and column names in referenced
|
to foreign key, referenced table name and column names in referenced
|
||||||
table which correspond to primary/unique key.
|
table which correspond to primary/unique key.
|
||||||
@ -18,8 +18,8 @@ one reference.
|
|||||||
|
|
||||||
check_foreign_key () is to used for primary/unique keys of a table.
|
check_foreign_key () is to used for primary/unique keys of a table.
|
||||||
|
|
||||||
You have to create trigger (BEFORE DELETE OR UPDATE) using this
|
You are to create trigger (BEFORE DELETE OR UPDATE) using this
|
||||||
function on a table referenced by another table(s). You have to specify
|
function on a table referenced by another table(s). You are to specify
|
||||||
as function arguments: number of references for which function has to
|
as function arguments: number of references for which function has to
|
||||||
performe checking, action if referencing key found ('cascade' - to delete
|
performe checking, action if referencing key found ('cascade' - to delete
|
||||||
corresponding foreign key, 'restrict' - to abort transaction if foreign keys
|
corresponding foreign key, 'restrict' - to abort transaction if foreign keys
|
||||||
@ -42,26 +42,20 @@ refint.source).
|
|||||||
|
|
||||||
Old internally supported time-travel (TT) used insert/delete
|
Old internally supported time-travel (TT) used insert/delete
|
||||||
transaction commit times. To get the same feature using triggers
|
transaction commit times. To get the same feature using triggers
|
||||||
you have to add to a table two columns of abstime type to store
|
you are to add to a table two columns of abstime type to store
|
||||||
date when a tuple was inserted (start_date) and changed/deleted
|
date when a tuple was inserted (start_date) and changed/deleted
|
||||||
(stop_date):
|
(stop_date):
|
||||||
|
|
||||||
CREATE TABLE XXX (
|
CREATE TABLE XXX (
|
||||||
... ...
|
... ...
|
||||||
date_on abstime,
|
date_on abstime default currabstime(),
|
||||||
date_off abstime
|
date_off abstime default 'infinity'
|
||||||
... ...
|
... ...
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TRIGGER timetravel
|
- so, tuples being inserted with NULLs in date_on/date_off will get
|
||||||
BEFORE INSERT OR DELETE OR UPDATE ON tttest
|
_current_date_ in date_on (name of start_date column in XXX) and INFINITY in
|
||||||
FOR EACH ROW
|
date_off (name of stop_date column in XXX).
|
||||||
EXECUTE PROCEDURE
|
|
||||||
timetravel (date_on, date_off);
|
|
||||||
|
|
||||||
Tuples being inserted with NULLs in date_on/date_off will get current
|
|
||||||
date in date_on (name of start_date column in XXX) and INFINITY in date_off
|
|
||||||
(name of stop_date column in XXX).
|
|
||||||
|
|
||||||
Tuples with stop_date equal INFINITY are "valid now": when trigger will
|
Tuples with stop_date equal INFINITY are "valid now": when trigger will
|
||||||
be fired for UPDATE/DELETE of a tuple with stop_date NOT equal INFINITY then
|
be fired for UPDATE/DELETE of a tuple with stop_date NOT equal INFINITY then
|
||||||
@ -78,7 +72,7 @@ DELETE: new tuple will be inserted with stop_date setted to current date
|
|||||||
(and with the same data in other columns as in tuple being deleted).
|
(and with the same data in other columns as in tuple being deleted).
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
1. To get tuples "valid now" you have to add _stop_date_ = 'infinity'
|
1. To get tuples "valid now" you are to add _stop_date_ = 'infinity'
|
||||||
to WHERE. Internally supported TT allowed to avoid this...
|
to WHERE. Internally supported TT allowed to avoid this...
|
||||||
Fixed rewriting RULEs could help here...
|
Fixed rewriting RULEs could help here...
|
||||||
As work arround you may use VIEWs...
|
As work arround you may use VIEWs...
|
||||||
@ -89,9 +83,12 @@ DELETE: new tuple will be inserted with stop_date setted to current date
|
|||||||
|
|
||||||
timetravel() is general trigger function.
|
timetravel() is general trigger function.
|
||||||
|
|
||||||
You have to create trigger BEFORE (!!!) INSERT OR UPDATE OR DELETE using
|
You are to create trigger BEFORE (!!!) UPDATE OR DELETE using this
|
||||||
this function on a time-traveled table. You have to specify two arguments:
|
function on a time-traveled table. You are to specify two arguments: name of
|
||||||
name of start_date column and name of stop_date column in triggered table.
|
start_date column and name of stop_date column in triggered table.
|
||||||
|
|
||||||
|
currabstime() may be used in DEFAULT for start_date column to get
|
||||||
|
current date.
|
||||||
|
|
||||||
set_timetravel() allows you turn time-travel ON/OFF for a table:
|
set_timetravel() allows you turn time-travel ON/OFF for a table:
|
||||||
|
|
||||||
@ -99,51 +96,9 @@ set_timetravel() allows you turn time-travel ON/OFF for a table:
|
|||||||
old status).
|
old status).
|
||||||
set_timetravel('XXX', 0) will turn TT OFF for table XXX (-"-).
|
set_timetravel('XXX', 0) will turn TT OFF for table XXX (-"-).
|
||||||
|
|
||||||
Turning TT OFF allows you do with a table ALL what you want!
|
Turning TT OFF allows you do with a table ALL what you want.
|
||||||
|
|
||||||
There is example in timetravel.example.
|
There is example in timetravel.example.
|
||||||
|
|
||||||
To CREATE FUNCTIONs use timetravel.sql (will be made by gmake from
|
To CREATE FUNCTIONs use timetravel.sql (will be made by gmake from
|
||||||
timetravel.source).
|
timetravel.source).
|
||||||
|
|
||||||
|
|
||||||
3. autoinc.c - function for implementing AUTOINCREMENT/IDENTITY feature.
|
|
||||||
|
|
||||||
You have to create BEFORE INSERT OR UPDATE trigger using function
|
|
||||||
autoinc(). You have to specify as function arguments: column name
|
|
||||||
(of int4 type) for which you want to get this feature and name of
|
|
||||||
SEQUENCE from which next value has to be fetched when NULL or 0
|
|
||||||
value is being inserted into column (, ... - you are able to specify
|
|
||||||
as many column/sequence pairs as you need).
|
|
||||||
|
|
||||||
There is example in autoinc.example.
|
|
||||||
|
|
||||||
To CREATE FUNCTION use autoinc.sql (will be made by gmake from
|
|
||||||
autoinc.source).
|
|
||||||
|
|
||||||
|
|
||||||
4. insert_username.c - function for inserting user names.
|
|
||||||
|
|
||||||
You have to create BEFORE INSERT OR UPDATE trigger using the function
|
|
||||||
insert_username(). You have to specify as a function argument: the column
|
|
||||||
name (of text type) in which user names will be inserted. Note that user
|
|
||||||
names will be inserted irregardless of the initial value of the field, so
|
|
||||||
that users cannot bypass this functionality by simply defining the field to
|
|
||||||
be NOT NULL.
|
|
||||||
|
|
||||||
There is an example in insert_username.example.
|
|
||||||
|
|
||||||
To CREATE FUNCTION use insert_username.sql (will be made by gmake from
|
|
||||||
insert_username.source).
|
|
||||||
|
|
||||||
|
|
||||||
5. moddatetime.c - function for maintaining a modification datetime stamp.
|
|
||||||
|
|
||||||
You have to create a BEFORE UPDATE trigger using the function moddatetime().
|
|
||||||
One argument must be given, that is the name of the field that is of type
|
|
||||||
datetime that is to be used as the modification time stamp.
|
|
||||||
|
|
||||||
There is an example in moddatetime.example.
|
|
||||||
|
|
||||||
To CREATE FUNCTION use moddatetime.sql ( will be made by gmake from
|
|
||||||
moddatetime.source).
|
|
||||||
|
68
contrib/spi/new_example.sql
Normal file
68
contrib/spi/new_example.sql
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
--Column ID of table A is primary key:
|
||||||
|
|
||||||
|
CREATE TABLE A (
|
||||||
|
ID int4 not null,
|
||||||
|
id1 int4 not null,
|
||||||
|
primary key (ID,ID1)
|
||||||
|
);
|
||||||
|
|
||||||
|
--Columns REFB of table B and REFC of C are foreign keys referenting ID of A:
|
||||||
|
|
||||||
|
CREATE TABLE B (
|
||||||
|
REFB int4,
|
||||||
|
REFB1 INT4
|
||||||
|
);
|
||||||
|
CREATE INDEX BI ON B (REFB);
|
||||||
|
|
||||||
|
CREATE TABLE C (
|
||||||
|
REFC int4,
|
||||||
|
REFC1 int4
|
||||||
|
);
|
||||||
|
CREATE INDEX CI ON C (REFC);
|
||||||
|
|
||||||
|
--Trigger for table A:
|
||||||
|
|
||||||
|
CREATE TRIGGER AT BEFORE DELETE ON A FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE
|
||||||
|
check_foreign_key (2, 'cascade', 'ID','id1', 'B', 'REFB','REFB1', 'C', 'REFC','REFC1');
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TRIGGER AT1 AFTER UPDATE ON A FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE
|
||||||
|
check_foreign_key (2, 'cascade', 'ID','id1', 'B', 'REFB','REFB1', 'C', 'REFC','REFC1');
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TRIGGER BT BEFORE INSERT OR UPDATE ON B FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE
|
||||||
|
check_primary_key ('REFB','REFB1', 'A', 'ID','ID1');
|
||||||
|
|
||||||
|
CREATE TRIGGER CT BEFORE INSERT OR UPDATE ON C FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE
|
||||||
|
check_primary_key ('REFC','REFC1', 'A', 'ID','ID1');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- Now try
|
||||||
|
|
||||||
|
INSERT INTO A VALUES (10,10);
|
||||||
|
INSERT INTO A VALUES (20,20);
|
||||||
|
INSERT INTO A VALUES (30,30);
|
||||||
|
INSERT INTO A VALUES (40,41);
|
||||||
|
INSERT INTO A VALUES (50,50);
|
||||||
|
|
||||||
|
INSERT INTO B VALUES (1); -- invalid reference
|
||||||
|
INSERT INTO B VALUES (10,10);
|
||||||
|
INSERT INTO B VALUES (30,30);
|
||||||
|
INSERT INTO B VALUES (30,30);
|
||||||
|
|
||||||
|
INSERT INTO C VALUES (11); -- invalid reference
|
||||||
|
INSERT INTO C VALUES (20,20);
|
||||||
|
INSERT INTO C VALUES (20,21);
|
||||||
|
INSERT INTO C VALUES (30,30);
|
||||||
|
|
||||||
|
-- now update work well
|
||||||
|
update A set ID = 100 , ID1 = 199 where ID=30 ;
|
||||||
|
|
||||||
|
SELECT * FROM A;
|
||||||
|
SELECT * FROM B;
|
||||||
|
SELECT * FROM C;
|
76
contrib/spi/preprocessor/README.MAX
Normal file
76
contrib/spi/preprocessor/README.MAX
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
|
||||||
|
Here are general trigger functions provided as workable examples
|
||||||
|
of using SPI and triggers. "General" means that functions may be
|
||||||
|
used for defining triggers for any tables but you have to specify
|
||||||
|
table/field names (as described below) while creating a trigger.
|
||||||
|
|
||||||
|
1. refint.c - functions for implementing referential integrity.
|
||||||
|
|
||||||
|
check_primary_key () is to used for foreign keys of a table.
|
||||||
|
|
||||||
|
You are to create trigger (BEFORE INSERT OR UPDATE) using this
|
||||||
|
function on a table referencing another table. You are to specify
|
||||||
|
as function arguments: triggered table column names which correspond
|
||||||
|
to foreign key, referenced table name and column names in referenced
|
||||||
|
table which correspond to primary/unique key.
|
||||||
|
You may create as many triggers as you need - one trigger for
|
||||||
|
one reference.
|
||||||
|
|
||||||
|
check_foreign_key () is to used for primary/unique keys of a table.
|
||||||
|
|
||||||
|
You are to create trigger (BEFORE DELETE OR UPDATE) using this
|
||||||
|
function on a table referenced by another table(s). You are to specify
|
||||||
|
as function arguments: number of references for which function has to
|
||||||
|
performe checking, action if referencing key found ('cascade' - to delete
|
||||||
|
corresponding foreign key, 'restrict' - to abort transaction if foreign keys
|
||||||
|
exist, 'setnull' - to set foreign key referencing primary/unique key
|
||||||
|
being deleted to null), triggered table column names which correspond
|
||||||
|
to primary/unique key, referencing table name and column names corresponding
|
||||||
|
to foreign key (, ... - as many referencing tables/keys as specified
|
||||||
|
by first argument).
|
||||||
|
Note, that NOT NULL constraint and unique index have to be defined by
|
||||||
|
youself.
|
||||||
|
|
||||||
|
There are examples in refint.example and regression tests
|
||||||
|
(sql/triggers.sql).
|
||||||
|
|
||||||
|
To CREATE FUNCTIONs use refint.sql (will be made by gmake from
|
||||||
|
refint.source).
|
||||||
|
|
||||||
|
# Excuse me for my bad english. Massimo Lambertini
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# New check foreign key
|
||||||
|
#
|
||||||
|
I think that cascade mode is to be considered like that the operation over
|
||||||
|
main table is to be made also in referenced table .
|
||||||
|
When i Delete , i must delete from referenced table ,
|
||||||
|
but when i update , i update referenced table and not delete like unmodified refint.c .
|
||||||
|
|
||||||
|
I made a patch that when i update it check the type of modified key ( if is a text , char() i
|
||||||
|
added '') and then create a update query that do the right think .
|
||||||
|
|
||||||
|
For my point of view that policy is helpfull because i do not have in referenced table
|
||||||
|
loss of information .
|
||||||
|
|
||||||
|
|
||||||
|
In preprocessor subdir i have placed a little utility that from a SQL92 table definition,
|
||||||
|
it create all trigger for foreign key .
|
||||||
|
|
||||||
|
|
||||||
|
the schema that i use to analyze the problem is this
|
||||||
|
|
||||||
|
create table
|
||||||
|
A
|
||||||
|
( key int4 not null primary key ,...) ;
|
||||||
|
create table
|
||||||
|
REFERENCED_B
|
||||||
|
( key int 4 , ... ,
|
||||||
|
foreign key ( key ) references A --
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
37
contrib/spi/preprocessor/example.sql
Normal file
37
contrib/spi/preprocessor/example.sql
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
-- Note the syntax is strict because i have no time to write better perl filter.
|
||||||
|
--
|
||||||
|
-- [blank] is 1 blank
|
||||||
|
-- at the end of an interesting line must be a [,] or [--]
|
||||||
|
-- [ending] must be a , or --
|
||||||
|
--
|
||||||
|
-- foreign[blank]key[blank]([blank]keyname,..,keyname[blank])[blank]references[blank]table[blank][ending]
|
||||||
|
--
|
||||||
|
-- step1.e < example.sql | step2.pl > foreign_key_triggers.sql
|
||||||
|
--
|
||||||
|
-- step1.e is a simple program that UPPERCASE ALL . I know that is simple implementing in Perl
|
||||||
|
-- bu i haven't time
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE
|
||||||
|
gruppo
|
||||||
|
(
|
||||||
|
codice_gruppo int4 NOT NULL,
|
||||||
|
descrizione varchar(32) NOT NULL
|
||||||
|
primary key ( codice_gruppo )
|
||||||
|
|
||||||
|
) ;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- fa_parte : Appartenenza di una Azienda Conatto o Cliente ad un certo GRUPPO
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE
|
||||||
|
fa_parte
|
||||||
|
(
|
||||||
|
codice_gruppo int4 NOT NULL,
|
||||||
|
codice_contatto int4 NOT NULL,
|
||||||
|
|
||||||
|
primary key ( codice_gruppo,codice_contatto ) ,
|
||||||
|
foreign key ( codice_gruppo ) references gruppo --
|
||||||
|
);
|
||||||
|
|
24
contrib/spi/preprocessor/step1.c
Normal file
24
contrib/spi/preprocessor/step1.c
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
char *strtoupper(char *string)
|
||||||
|
{
|
||||||
|
int i ;
|
||||||
|
for (i=0;i<strlen(string);i++)
|
||||||
|
{
|
||||||
|
string[i]=toupper(string[i]);
|
||||||
|
}
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void main ( char argc , char **argv )
|
||||||
|
{
|
||||||
|
char str[250];
|
||||||
|
int sw = 0 ;
|
||||||
|
while ( fgets (str,240,stdin) )
|
||||||
|
{
|
||||||
|
if ( sw == 0 ) printf("%s",strtoupper(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
BIN
contrib/spi/preprocessor/step1.e
Executable file
BIN
contrib/spi/preprocessor/step1.e
Executable file
Binary file not shown.
123
contrib/spi/preprocessor/step2.pl
Executable file
123
contrib/spi/preprocessor/step2.pl
Executable file
@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
##
|
||||||
|
## MAIN
|
||||||
|
##
|
||||||
|
$table_name="";
|
||||||
|
$old_name="";
|
||||||
|
$references_table="";
|
||||||
|
$references_column="";
|
||||||
|
$is_create=0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
while ( <> )
|
||||||
|
{
|
||||||
|
chop;
|
||||||
|
$str=$_ ;
|
||||||
|
|
||||||
|
if ($is_create == 1) {
|
||||||
|
$table_name=$str;
|
||||||
|
$is_create=2;
|
||||||
|
}
|
||||||
|
if ( $str =~ /^CREATE TABLE/ ){
|
||||||
|
$is_create=1;
|
||||||
|
}
|
||||||
|
if ($is_create == 2) {
|
||||||
|
if ($str =~ /^FOREIGN KEY/){
|
||||||
|
($d1,$d2,$d3,$columns,$d4,$d5,$references_table,$d6) = split (/ /,$str,8);
|
||||||
|
#printf "Table $table_name $columns $references_table\n";
|
||||||
|
|
||||||
|
if ($table_name ne $old_name ){
|
||||||
|
printf "--\n-- Trigger for $table_name\n--\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach $i ( split(/,/ , $columns ) ){
|
||||||
|
print "CREATE INDEX I_$table_name";
|
||||||
|
print "_$i ON $table_name ( $i ) ;\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
printf "\nCREATE TRIGGER T_P_$table_name";
|
||||||
|
printf "_$references_table BEFORE INSERT OR UPDATE ON $table_name FOR EACH ROW\n" ;
|
||||||
|
printf "EXECUTE PROCEDURE\n";
|
||||||
|
printf "check_primary_key(";
|
||||||
|
$val=0;
|
||||||
|
foreach $i ( split(/,/ , $columns ) ){
|
||||||
|
print "'$i',";
|
||||||
|
$val=$val+1 ;
|
||||||
|
}
|
||||||
|
print "'$references_table',";
|
||||||
|
|
||||||
|
$t=1;
|
||||||
|
foreach $i ( split(/,/,$columns ) ){
|
||||||
|
print "'$i'";
|
||||||
|
if ( $t < $val ) {
|
||||||
|
printf ",";
|
||||||
|
}
|
||||||
|
$t=$t+1;
|
||||||
|
}
|
||||||
|
print " );\n\n";
|
||||||
|
|
||||||
|
printf "CREATE TRIGGER T_F_D_$references_table";
|
||||||
|
printf "_$table_name BEFORE DELETE ON $references_table FOR EACH ROW\n" ;
|
||||||
|
printf "EXECUTE PROCEDURE\n";
|
||||||
|
printf "check_foreign_key(1,'cascade',";
|
||||||
|
$val=0;
|
||||||
|
foreach $i ( split(/,/ , $columns ) ){
|
||||||
|
print "'$i',";
|
||||||
|
$val=$val+1 ;
|
||||||
|
}
|
||||||
|
print "'$table_name',";
|
||||||
|
|
||||||
|
$t=1;
|
||||||
|
foreach $i ( split(/,/,$columns ) ){
|
||||||
|
print "'$i'";
|
||||||
|
if ( $t < $val ) {
|
||||||
|
printf ",";
|
||||||
|
}
|
||||||
|
$t=$t+1;
|
||||||
|
}
|
||||||
|
print " );\n\n";
|
||||||
|
|
||||||
|
printf "CREATE TRIGGER T_F_U_$references_table";
|
||||||
|
printf "_$table_name AFTER UPDATE ON $references_table FOR EACH ROW\n" ;
|
||||||
|
printf "EXECUTE PROCEDURE\n";
|
||||||
|
printf "check_foreign_key(1,'cascade',";
|
||||||
|
$val=0;
|
||||||
|
foreach $i ( split(/,/ , $columns ) ){
|
||||||
|
print "'$i',";
|
||||||
|
$val=$val+1 ;
|
||||||
|
}
|
||||||
|
print "'$table_name',";
|
||||||
|
|
||||||
|
$t=1;
|
||||||
|
foreach $i ( split(/,/,$columns ) ){
|
||||||
|
print "'$i'";
|
||||||
|
if ( $t < $val ) {
|
||||||
|
printf ",";
|
||||||
|
}
|
||||||
|
$t=$t+1;
|
||||||
|
}
|
||||||
|
print " );\n\n";
|
||||||
|
|
||||||
|
if ($table_name ne $old_name ){
|
||||||
|
printf "-- ********************************\n\n\n";
|
||||||
|
}
|
||||||
|
$old_name=$table_name ;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($str =~ /^\)\;/ ) {
|
||||||
|
$is_create = 0 ;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3,10 +3,15 @@
|
|||||||
* constraints using general triggers.
|
* constraints using general triggers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define DEBUG_QUERY 1
|
||||||
|
|
||||||
#include "executor/spi.h" /* this is what you need to work with SPI */
|
#include "executor/spi.h" /* this is what you need to work with SPI */
|
||||||
#include "commands/trigger.h" /* -"- and triggers */
|
#include "commands/trigger.h" /* -"- and triggers */
|
||||||
#include <ctype.h> /* tolower () */
|
#include <ctype.h> /* tolower () */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
HeapTuple check_primary_key(void);
|
HeapTuple check_primary_key(void);
|
||||||
HeapTuple check_foreign_key(void);
|
HeapTuple check_foreign_key(void);
|
||||||
|
|
||||||
@ -30,9 +35,9 @@ static EPlan *find_plan(char *ident, EPlan ** eplan, int *nplans);
|
|||||||
* references existing tuple in "primary" table.
|
* references existing tuple in "primary" table.
|
||||||
* Though it's called without args You have to specify referenced
|
* Though it's called without args You have to specify referenced
|
||||||
* table/keys while creating trigger: key field names in triggered table,
|
* table/keys while creating trigger: key field names in triggered table,
|
||||||
* referenced table name, referenced key field names,type of action [automatic|dependent]:
|
* referenced table name, referenced key field names:
|
||||||
* EXECUTE PROCEDURE
|
* EXECUTE PROCEDURE
|
||||||
* check_primary_key ('Fkey1', 'Fkey2', 'Ptable', 'Pkey1', 'Pkey2','[automatic|dependent]').
|
* check_primary_key ('Fkey1', 'Fkey2', 'Ptable', 'Pkey1', 'Pkey2').
|
||||||
*/
|
*/
|
||||||
|
|
||||||
HeapTuple /* have to return HeapTuple to Executor */
|
HeapTuple /* have to return HeapTuple to Executor */
|
||||||
@ -41,10 +46,9 @@ check_primary_key()
|
|||||||
Trigger *trigger; /* to get trigger name */
|
Trigger *trigger; /* to get trigger name */
|
||||||
int nargs; /* # of args specified in CREATE TRIGGER */
|
int nargs; /* # of args specified in CREATE TRIGGER */
|
||||||
char **args; /* arguments: column names and table name */
|
char **args; /* arguments: column names and table name */
|
||||||
int nkeys; /* # of key columns (= (nargs-1) / 2) */
|
int nkeys; /* # of key columns (= nargs / 2) */
|
||||||
Datum *kvals; /* key values */
|
Datum *kvals; /* key values */
|
||||||
char *relname; /* referenced relation name */
|
char *relname; /* referenced relation name */
|
||||||
char *action; /* action on insert or update*/
|
|
||||||
Relation rel; /* triggered relation */
|
Relation rel; /* triggered relation */
|
||||||
HeapTuple tuple = NULL; /* tuple to return */
|
HeapTuple tuple = NULL; /* tuple to return */
|
||||||
TupleDesc tupdesc; /* tuple description */
|
TupleDesc tupdesc; /* tuple description */
|
||||||
@ -58,7 +62,9 @@ check_primary_key()
|
|||||||
/*
|
/*
|
||||||
* Some checks first...
|
* Some checks first...
|
||||||
*/
|
*/
|
||||||
|
#ifdef DEBUG_QUERY
|
||||||
|
elog(NOTICE,"Check_primary_key Enter Function");
|
||||||
|
#endif
|
||||||
/* Called by trigger manager ? */
|
/* Called by trigger manager ? */
|
||||||
if (!CurrentTriggerData)
|
if (!CurrentTriggerData)
|
||||||
elog(ERROR, "check_primary_key: triggers are not initialized");
|
elog(ERROR, "check_primary_key: triggers are not initialized");
|
||||||
@ -69,12 +75,10 @@ check_primary_key()
|
|||||||
|
|
||||||
/* If INSERTion then must check Tuple to being inserted */
|
/* If INSERTion then must check Tuple to being inserted */
|
||||||
if (TRIGGER_FIRED_BY_INSERT(CurrentTriggerData->tg_event))
|
if (TRIGGER_FIRED_BY_INSERT(CurrentTriggerData->tg_event))
|
||||||
|
|
||||||
tuple = CurrentTriggerData->tg_trigtuple;
|
tuple = CurrentTriggerData->tg_trigtuple;
|
||||||
|
|
||||||
/* Not should be called for DELETE */
|
/* Not should be called for DELETE */
|
||||||
else if (TRIGGER_FIRED_BY_DELETE(CurrentTriggerData->tg_event))
|
else if (TRIGGER_FIRED_BY_DELETE(CurrentTriggerData->tg_event))
|
||||||
|
|
||||||
elog(ERROR, "check_primary_key: can't process DELETE events");
|
elog(ERROR, "check_primary_key: can't process DELETE events");
|
||||||
|
|
||||||
/* If UPDATion the must check new Tuple, not old one */
|
/* If UPDATion the must check new Tuple, not old one */
|
||||||
@ -85,14 +89,10 @@ check_primary_key()
|
|||||||
nargs = trigger->tgnargs;
|
nargs = trigger->tgnargs;
|
||||||
args = trigger->tgargs;
|
args = trigger->tgargs;
|
||||||
|
|
||||||
if ((nargs-1) % 2 != 1) /* odd number of arguments! */
|
if (nargs % 2 != 1) /* odd number of arguments! */
|
||||||
elog(ERROR, "check_primary_key: even number of arguments should be specified");
|
elog(ERROR, "check_primary_key: odd number of arguments should be specified");
|
||||||
|
|
||||||
nkeys = (nargs-1) / 2;
|
nkeys = nargs / 2;
|
||||||
action=args[nargs -1];
|
|
||||||
if (strcmp(action,"automatic") && strcmp(action,"dependent"))
|
|
||||||
elog(ERROR,"check_primary_key: unknown action");
|
|
||||||
nargs=nargs-1;
|
|
||||||
relname = args[nkeys];
|
relname = args[nkeys];
|
||||||
rel = CurrentTriggerData->tg_relation;
|
rel = CurrentTriggerData->tg_relation;
|
||||||
tupdesc = rel->rd_att;
|
tupdesc = rel->rd_att;
|
||||||
@ -203,62 +203,9 @@ check_primary_key()
|
|||||||
/*
|
/*
|
||||||
* If there are no tuples returned by SELECT then ...
|
* If there are no tuples returned by SELECT then ...
|
||||||
*/
|
*/
|
||||||
if (SPI_processed == 0 && strcmp(action,"dependent")==0)
|
if (SPI_processed == 0)
|
||||||
elog(ERROR, "%s: tuple references non-existing key in %s",
|
elog(ERROR, "%s: tuple references non-existing key in %s",
|
||||||
trigger->tgname, relname);
|
trigger->tgname, relname);
|
||||||
else if (strcmp(action,"automatic")==0)
|
|
||||||
{
|
|
||||||
/* insert tuple in parent with only primary keys */
|
|
||||||
/* prepare plan */
|
|
||||||
void *pplan;
|
|
||||||
char sql[8192];
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Construct query:INSERT INTO relname (Pkey1[,Pkey2]*) values ($1,$2..);
|
|
||||||
*/
|
|
||||||
sprintf(sql, "insert into %s ( ", relname);
|
|
||||||
for (i = 0; i < nkeys; i++)
|
|
||||||
{
|
|
||||||
sprintf(sql + strlen(sql), "%s%s ", args[i + nkeys + 1],(i<nkeys-1) ? ",":"");
|
|
||||||
}
|
|
||||||
sprintf(sql+strlen(sql),") values (");
|
|
||||||
for (i=0;i<nkeys; i++)
|
|
||||||
{
|
|
||||||
sprintf(sql+strlen(sql),"$%d%s ",i+1,(i<nkeys-1) ? ",":"");
|
|
||||||
}
|
|
||||||
sprintf(sql+strlen(sql),")");
|
|
||||||
|
|
||||||
/* Prepare plan for query */
|
|
||||||
pplan = SPI_prepare(sql, nkeys, argtypes);
|
|
||||||
if (pplan == NULL)
|
|
||||||
elog(ERROR, "check_primary_key: SPI_prepare returned %d", SPI_result);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Remember that SPI_prepare places plan in current memory context
|
|
||||||
* - so, we have to save plan in Top memory context for latter
|
|
||||||
* use.
|
|
||||||
*/
|
|
||||||
pplan = SPI_saveplan(pplan);
|
|
||||||
if (pplan == NULL)
|
|
||||||
elog(ERROR, "check_primary_key: SPI_saveplan returned %d", SPI_result);
|
|
||||||
plan->splan = (void **) malloc(sizeof(void *));
|
|
||||||
*(plan->splan) = pplan;
|
|
||||||
plan->nplans = 1;
|
|
||||||
/*
|
|
||||||
* Ok, execute prepared plan.
|
|
||||||
*/
|
|
||||||
ret = SPI_execp(*(plan->splan), kvals, NULL, 1);
|
|
||||||
/* we have no NULLs - so we pass ^^^^ here */
|
|
||||||
|
|
||||||
if (ret < 0)
|
|
||||||
elog(ERROR, "check_primary_key: SPI_execp returned %d", ret);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If there are no tuples returned by INSERT then ...
|
|
||||||
*/
|
|
||||||
if (SPI_processed == 0)
|
|
||||||
elog(ERROR, "error: can't enter automatically in %s",relname);
|
|
||||||
}
|
|
||||||
|
|
||||||
SPI_finish();
|
SPI_finish();
|
||||||
|
|
||||||
@ -283,6 +230,7 @@ check_foreign_key()
|
|||||||
Trigger *trigger; /* to get trigger name */
|
Trigger *trigger; /* to get trigger name */
|
||||||
int nargs; /* # of args specified in CREATE TRIGGER */
|
int nargs; /* # of args specified in CREATE TRIGGER */
|
||||||
char **args; /* arguments: as described above */
|
char **args; /* arguments: as described above */
|
||||||
|
char **args_temp ;
|
||||||
int nrefs; /* number of references (== # of plans) */
|
int nrefs; /* number of references (== # of plans) */
|
||||||
char action; /* 'R'estrict | 'S'etnull | 'C'ascade */
|
char action; /* 'R'estrict | 'S'etnull | 'C'ascade */
|
||||||
int nkeys; /* # of key columns */
|
int nkeys; /* # of key columns */
|
||||||
@ -298,10 +246,13 @@ check_foreign_key()
|
|||||||
bool isequal = true; /* are keys in both tuples equal (in
|
bool isequal = true; /* are keys in both tuples equal (in
|
||||||
* UPDATE) */
|
* UPDATE) */
|
||||||
char ident[2 * NAMEDATALEN]; /* to identify myself */
|
char ident[2 * NAMEDATALEN]; /* to identify myself */
|
||||||
|
int is_update=0;
|
||||||
int ret;
|
int ret;
|
||||||
int i,
|
int i,
|
||||||
r;
|
r;
|
||||||
|
#ifdef DEBUG_QUERY
|
||||||
|
elog(NOTICE,"Check_foreign_key Enter Function");
|
||||||
|
#endif
|
||||||
/*
|
/*
|
||||||
* Some checks first...
|
* Some checks first...
|
||||||
*/
|
*/
|
||||||
@ -316,7 +267,6 @@ check_foreign_key()
|
|||||||
|
|
||||||
/* Not should be called for INSERT */
|
/* Not should be called for INSERT */
|
||||||
if (TRIGGER_FIRED_BY_INSERT(CurrentTriggerData->tg_event))
|
if (TRIGGER_FIRED_BY_INSERT(CurrentTriggerData->tg_event))
|
||||||
|
|
||||||
elog(ERROR, "check_foreign_key: can't process INSERT events");
|
elog(ERROR, "check_foreign_key: can't process INSERT events");
|
||||||
|
|
||||||
/* Have to check tg_trigtuple - tuple being deleted */
|
/* Have to check tg_trigtuple - tuple being deleted */
|
||||||
@ -327,9 +277,12 @@ check_foreign_key()
|
|||||||
* key in tg_newtuple is the same as in tg_trigtuple then nothing to
|
* key in tg_newtuple is the same as in tg_trigtuple then nothing to
|
||||||
* do.
|
* do.
|
||||||
*/
|
*/
|
||||||
|
is_update=0;
|
||||||
if (TRIGGER_FIRED_BY_UPDATE(CurrentTriggerData->tg_event))
|
if (TRIGGER_FIRED_BY_UPDATE(CurrentTriggerData->tg_event))
|
||||||
|
{
|
||||||
newtuple = CurrentTriggerData->tg_newtuple;
|
newtuple = CurrentTriggerData->tg_newtuple;
|
||||||
|
is_update=1;
|
||||||
|
}
|
||||||
trigger = CurrentTriggerData->tg_trigger;
|
trigger = CurrentTriggerData->tg_trigger;
|
||||||
nargs = trigger->tgnargs;
|
nargs = trigger->tgnargs;
|
||||||
args = trigger->tgargs;
|
args = trigger->tgargs;
|
||||||
@ -337,6 +290,7 @@ check_foreign_key()
|
|||||||
if (nargs < 5) /* nrefs, action, key, Relation, key - at
|
if (nargs < 5) /* nrefs, action, key, Relation, key - at
|
||||||
* least */
|
* least */
|
||||||
elog(ERROR, "check_foreign_key: too short %d (< 5) list of arguments", nargs);
|
elog(ERROR, "check_foreign_key: too short %d (< 5) list of arguments", nargs);
|
||||||
|
|
||||||
nrefs = pg_atoi(args[0], sizeof(int), 0);
|
nrefs = pg_atoi(args[0], sizeof(int), 0);
|
||||||
if (nrefs < 1)
|
if (nrefs < 1)
|
||||||
elog(ERROR, "check_foreign_key: %d (< 1) number of references specified", nrefs);
|
elog(ERROR, "check_foreign_key: %d (< 1) number of references specified", nrefs);
|
||||||
@ -434,6 +388,7 @@ check_foreign_key()
|
|||||||
if (plan->nplans <= 0) /* Get typeId of column */
|
if (plan->nplans <= 0) /* Get typeId of column */
|
||||||
argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
|
argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
|
||||||
}
|
}
|
||||||
|
args_temp = args;
|
||||||
nargs -= nkeys;
|
nargs -= nkeys;
|
||||||
args += nkeys;
|
args += nkeys;
|
||||||
|
|
||||||
@ -444,14 +399,13 @@ check_foreign_key()
|
|||||||
{
|
{
|
||||||
void *pplan;
|
void *pplan;
|
||||||
char sql[8192];
|
char sql[8192];
|
||||||
char **args2 = args;
|
char **args2 = args ;
|
||||||
|
|
||||||
plan->splan = (void **) malloc(nrefs * sizeof(void *));
|
plan->splan = (void **) malloc(nrefs * sizeof(void *));
|
||||||
|
|
||||||
for (r = 0; r < nrefs; r++)
|
for (r = 0; r < nrefs; r++)
|
||||||
{
|
{
|
||||||
relname = args2[0];
|
relname = args2[0];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For 'R'estrict action we construct SELECT query - SELECT 1
|
* For 'R'estrict action we construct SELECT query - SELECT 1
|
||||||
* FROM _referencing_relation_ WHERE Fkey1 = $1 [AND Fkey2 =
|
* FROM _referencing_relation_ WHERE Fkey1 = $1 [AND Fkey2 =
|
||||||
@ -465,11 +419,50 @@ check_foreign_key()
|
|||||||
* For 'C'ascade action we construct DELETE query - DELETE
|
* For 'C'ascade action we construct DELETE query - DELETE
|
||||||
* FROM _referencing_relation_ WHERE Fkey1 = $1 [AND Fkey2 =
|
* FROM _referencing_relation_ WHERE Fkey1 = $1 [AND Fkey2 =
|
||||||
* $2 [...]] - to delete all referencing tuples.
|
* $2 [...]] - to delete all referencing tuples.
|
||||||
*/
|
*/
|
||||||
else if (action == 'c')
|
/*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 ;
|
||||||
|
sprintf(sql, "update %s set ", relname);
|
||||||
|
for (k = 1; k <= nkeys; k++)
|
||||||
|
{
|
||||||
|
int is_char_type =0;
|
||||||
|
char *type;
|
||||||
|
|
||||||
|
fn = SPI_fnumber(tupdesc, args_temp[k-1]);
|
||||||
|
nv = SPI_getvalue(newtuple, tupdesc, fn);
|
||||||
|
type=SPI_gettype(tupdesc,fn);
|
||||||
|
|
||||||
|
if ( (strcmp(type,"text") && strcmp (type,"varchar") &&
|
||||||
|
strcmp(type,"char") && strcmp (type,"bpchar") &&
|
||||||
|
strcmp(type,"date") && strcmp (type,"datetime")) == 0 )
|
||||||
|
is_char_type=1;
|
||||||
|
#ifdef DEBUG_QUERY
|
||||||
|
elog(NOTICE,"Check_foreign_key Debug value %s type %s %d",
|
||||||
|
nv,type,is_char_type);
|
||||||
|
#endif
|
||||||
|
/* is_char_type =1 i set ' ' for define a new value
|
||||||
|
*/
|
||||||
|
sprintf(sql + strlen(sql), " %s = %s%s%s %s ",
|
||||||
|
args2[k], (is_char_type>0) ? "'" :"" ,
|
||||||
|
nv, (is_char_type >0) ? "'" :"",(k < nkeys) ? ", " : "");
|
||||||
|
is_char_type=0;
|
||||||
|
}
|
||||||
|
strcat(sql, " where ");
|
||||||
|
|
||||||
|
}
|
||||||
|
else /* DELETE */
|
||||||
sprintf(sql, "delete from %s where ", relname);
|
sprintf(sql, "delete from %s where ", relname);
|
||||||
|
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* For 'S'etnull action we construct UPDATE query - UPDATE
|
* For 'S'etnull action we construct UPDATE query - UPDATE
|
||||||
* _referencing_relation_ SET Fkey1 null [, Fkey2 null [...]]
|
* _referencing_relation_ SET Fkey1 null [, Fkey2 null [...]]
|
||||||
@ -509,12 +502,15 @@ check_foreign_key()
|
|||||||
elog(ERROR, "check_foreign_key: SPI_saveplan returned %d", SPI_result);
|
elog(ERROR, "check_foreign_key: SPI_saveplan returned %d", SPI_result);
|
||||||
|
|
||||||
plan->splan[r] = pplan;
|
plan->splan[r] = pplan;
|
||||||
|
|
||||||
args2 += nkeys + 1; /* to the next relation */
|
args2 += nkeys + 1; /* to the next relation */
|
||||||
}
|
}
|
||||||
plan->nplans = nrefs;
|
plan->nplans = nrefs;
|
||||||
|
#ifdef DEBUG_QUERY
|
||||||
|
elog(NOTICE,"Check_foreign_key Debug Query is : %s ", sql);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If UPDATE and key is not changed ...
|
* If UPDATE and key is not changed ...
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user