2 -- Create the tables used in the test queries
4 -- T_pkey1 is the primary key table for T_dta1. Entries from T_pkey1
5 -- Cannot be changed or deleted if they are referenced from T_dta1.
7 -- T_pkey2 is the primary key table for T_dta2. If the key values in
8 -- T_pkey2 are changed, the references in T_dta2 follow. If entries
9 -- are deleted, the referencing entries from T_dta2 are deleted too.
10 -- The values for field key2 in T_pkey2 are silently converted to
11 -- upper case on insert/update.
13 create table T_pkey1 (
18 create table T_pkey2 (
34 -- Function to check key existance in T_pkey1
36 create function check_pkey1_exists(int4, bpchar) returns bool as E'
37 if {![info exists GD]} {
38 set GD(plan) [spi_prepare \\
39 "select 1 from T_pkey1 \\
40 where key1 = \\$1 and key2 = \\$2" \\
44 set n [spi_execp -count 1 $GD(plan) [list $1 $2]]
52 CREATE TABLE trigger_test
54 CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$
56 if { [info exists TG_relid] } {
57 set TG_relid "bogus:12345"
60 set dnames [info locals {[a-zA-Z]*} ]
62 foreach key [lsort $dnames] {
64 if { [array exists $key] } {
66 foreach akey [lsort [ array names $key ] ] {
67 if {[string length $str] > 1} { set str "$str, " }
69 set cmd "set val \$$key$cmd"
71 set str "$str$akey: $val"
74 elog NOTICE "$key: $str"
76 set val [eval list "\$$key" ]
77 elog NOTICE "$key: $val"
85 CREATE TRIGGER show_trigger_data_trig
86 BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
87 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
89 -- Trigger function on every change to T_pkey1
91 create function trig_pkey1_before() returns trigger as E'
93 # Create prepared plans on the first call
95 if {![info exists GD]} {
97 # Plan to check for duplicate key in T_pkey1
99 set GD(plan_pkey1) [spi_prepare \\
100 "select check_pkey1_exists(\\$1, \\$2) as ret" \\
103 # Plan to check for references from T_dta1
105 set GD(plan_dta1) [spi_prepare \\
106 "select 1 from T_dta1 \\
107 where ref1 = \\$1 and ref2 = \\$2" \\
120 # Must check for duplicate key on INSERT
126 # Must check for duplicate key on UPDATE only if
127 # the key changes. In that case we must check for
128 # references to OLD values too.
130 if {[string compare $NEW(key1) $OLD(key1)] != 0} {
134 if {[string compare $NEW(key2) $OLD(key2)] != 0} {
141 # Must only check for references to OLD on DELETE
147 if {$check_new_dup} {
149 # Check for duplicate key
151 spi_execp -count 1 $GD(plan_pkey1) [list $NEW(key1) $NEW(key2)]
154 "duplicate key ''$NEW(key1)'', ''$NEW(key2)'' for T_pkey1"
158 if {$check_old_ref} {
160 # Check for references to OLD
162 set n [spi_execp -count 1 $GD(plan_dta1) [list $OLD(key1) $OLD(key2)]]
165 "key ''$OLD(key1)'', ''$OLD(key2)'' referenced by T_dta1"
170 # Anything is fine - let operation pass through
174 create trigger pkey1_before before insert or update or delete on T_pkey1
175 for each row execute procedure
178 -- Trigger function to check for duplicate keys in T_pkey2
179 -- and to force key2 to be upper case only without leading whitespaces
181 create function trig_pkey2_before() returns trigger as E'
183 # Prepare plan on first call
185 if {![info exists GD]} {
186 set GD(plan_pkey2) [spi_prepare \\
187 "select 1 from T_pkey2 \\
188 where key1 = \\$1 and key2 = \\$2" \\
195 set NEW(key2) [string toupper [string trim $NEW(key2)]]
198 # Check for duplicate key
200 set n [spi_execp -count 1 $GD(plan_pkey2) [list $NEW(key1) $NEW(key2)]]
203 "duplicate key ''$NEW(key1)'', ''$NEW(key2)'' for T_pkey2"
207 # Return modified tuple in NEW
209 return [array get NEW]
211 create trigger pkey2_before before insert or update on T_pkey2
212 for each row execute procedure
215 -- Trigger function to force references from T_dta2 follow changes
216 -- in T_pkey2 or be deleted too. This must be done AFTER the changes
217 -- in T_pkey2 are done so the trigger for primkey check on T_dta2
218 -- fired on our updates will see the new key values in T_pkey2.
220 create function trig_pkey2_after() returns trigger as E'
222 # Prepare plans on first call
224 if {![info exists GD]} {
226 # Plan to update references from T_dta2
228 set GD(plan_dta2_upd) [spi_prepare \\
229 "update T_dta2 set ref1 = \\$3, ref2 = \\$4 \\
230 where ref1 = \\$1 and ref2 = \\$2" \\
231 {int4 bpchar int4 bpchar}]
233 # Plan to delete references from T_dta2
235 set GD(plan_dta2_del) [spi_prepare \\
236 "delete from T_dta2 \\
237 where ref1 = \\$1 and ref2 = \\$2" \\
250 # On update we must let old references follow
252 set NEW(key2) [string toupper $NEW(key2)]
254 if {[string compare $NEW(key1) $OLD(key1)] != 0} {
257 if {[string compare $NEW(key2) $OLD(key2)] != 0} {
263 # On delete we must delete references too
269 if {$old_ref_follow} {
271 # Let old references follow and fire NOTICE message if
274 set n [spi_execp $GD(plan_dta2_upd) \\
275 [list $OLD(key1) $OLD(key2) $NEW(key1) $NEW(key2)]]
278 "updated $n entries in T_dta2 for new key in T_pkey2"
282 if {$old_ref_delete} {
284 # delete references and fire NOTICE message if
287 set n [spi_execp $GD(plan_dta2_del) \\
288 [list $OLD(key1) $OLD(key2)]]
291 "deleted $n entries from T_dta2"
297 create trigger pkey2_after after update or delete on T_pkey2
298 for each row execute procedure
301 -- Generic trigger function to check references in T_dta1 and T_dta2
303 create function check_primkey() returns trigger as E'
305 # For every trigger/relation pair we create
306 # a saved plan and hold them in GD
308 set plankey [list "plan" $TG_name $TG_relid]
309 set planrel [list "relname" $TG_relid]
312 # Extract the pkey relation name
314 set keyidx [expr [llength $args] / 2]
315 set keyrel [string tolower [lindex $args $keyidx]]
317 if {![info exists GD($plankey)]} {
319 # We must prepare a new plan. Build up a query string
320 # for the primary key check.
322 set keylist [lrange $args [expr $keyidx + 1] end]
324 set query "select 1 from $keyrel"
328 foreach key $keylist {
329 set key [string tolower $key]
331 # Add the qual part to the query string
333 append query "$qual $key = \\$$idx"
337 # Lookup the fields type in pg_attribute
339 set n [spi_exec "select T.typname \\
340 from pg_catalog.pg_type T, pg_catalog.pg_attribute A, pg_catalog.pg_class C \\
341 where C.relname = ''[quote $keyrel]'' \\
342 and C.oid = A.attrelid \\
343 and A.attname = ''[quote $key]'' \\
344 and A.atttypid = T.oid"]
346 elog ERROR "table $keyrel doesn''t have a field named $key"
350 # Append the fields type to the argument type list
352 lappend typlist $typname
359 set GD($plankey) [spi_prepare $query $typlist]
362 # Lookup and remember the table name for later error messages
364 spi_exec "select relname from pg_catalog.pg_class \\
365 where oid = ''$TG_relid''::oid"
366 set GD($planrel) $relname
370 # Build the argument list from the NEW row
374 foreach arg [lrange $args 0 $keyidx] {
375 lappend arglist $NEW($arg)
379 # Check for the primary key
381 set n [spi_execp -count 1 $GD($plankey) $arglist]
383 elog ERROR "key for $GD($planrel) not in $keyrel"
391 create trigger dta1_before before insert or update on T_dta1
392 for each row execute procedure
393 check_primkey('ref1', 'ref2', 'T_pkey1', 'key1', 'key2');
394 create trigger dta2_before before insert or update on T_dta2
395 for each row execute procedure
396 check_primkey('ref1', 'ref2', 'T_pkey2', 'key1', 'key2');
397 create function tcl_int4add(int4,int4) returns int4 as '
398 return [expr $1 + $2]
400 -- We use split(n) as a quick-and-dirty way of parsing the input array
401 -- value, which comes in as a string like '{1,2}'. There are better ways...
402 create function tcl_int4_accum(int4[], int4) returns int4[] as '
403 set state [split $1 "{,}"]
404 set newsum [expr {[lindex $state 1] + $2}]
405 set newcnt [expr {[lindex $state 2] + 1}]
406 return "{$newsum,$newcnt}"
408 create function tcl_int4_avg(int4[]) returns int4 as '
409 set state [split $1 "{,}"]
410 if {[lindex $state 2] == 0} { return_null }
411 return [expr {[lindex $state 1] / [lindex $state 2]}]
413 create aggregate tcl_avg (
414 sfunc = tcl_int4_accum,
417 finalfunc = tcl_int4_avg,
420 create aggregate tcl_sum (
426 create function tcl_int4lt(int4,int4) returns bool as '
432 create function tcl_int4le(int4,int4) returns bool as '
438 create function tcl_int4eq(int4,int4) returns bool as '
444 create function tcl_int4ge(int4,int4) returns bool as '
450 create function tcl_int4gt(int4,int4) returns bool as '
459 procedure = tcl_int4lt
461 create operator @<= (
464 procedure = tcl_int4le
469 procedure = tcl_int4eq
471 create operator @>= (
474 procedure = tcl_int4ge
479 procedure = tcl_int4gt
481 create function tcl_int4cmp(int4,int4) returns int4 as '
490 CREATE OPERATOR CLASS tcl_int4_ops
491 FOR TYPE int4 USING btree AS
497 FUNCTION 1 tcl_int4cmp(int4,int4) ;