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 (
19 create table T_pkey2 (
39 -- Function to check key existence in T_pkey1
41 create function check_pkey1_exists(int4, bpchar) returns bool as E'
42 if {![info exists GD]} {
43 set GD(plan) [spi_prepare \\
44 "select 1 from T_pkey1 \\
45 where key1 = \\$1 and key2 = \\$2" \\
49 set n [spi_execp -count 1 $GD(plan) [list $1 $2]]
60 CREATE TABLE trigger_test (
64 test_skip boolean DEFAULT false,
65 test_return_null boolean DEFAULT false,
66 test_argisnull boolean DEFAULT false
68 -- Make certain dropped attributes are handled correctly
69 ALTER TABLE trigger_test DROP dropme;
71 CREATE VIEW trigger_test_view AS SELECT i, v FROM trigger_test;
73 CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$
74 if {$TG_table_name eq "trigger_test" && $TG_level eq "ROW" && $TG_op ne "DELETE"} {
76 if {$NEW(test_return_null) eq "t" } {
79 if {$NEW(test_argisnull) eq "t" } {
80 set should_error [argisnull 1]
82 if {$NEW(test_skip) eq "t" } {
83 elog NOTICE "SKIPPING OPERATION $TG_op"
88 if { [info exists TG_relid] } {
89 set TG_relid "bogus:12345"
92 set dnames [info locals {[a-zA-Z]*} ]
94 foreach key [lsort $dnames] {
96 if { [array exists $key] } {
98 foreach akey [lsort [ array names $key ] ] {
99 if {[string length $str] > 1} { set str "$str, " }
101 set cmd "set val \$$key$cmd"
103 set str "$str$akey: $val"
106 elog NOTICE "$key: $str"
108 set val [eval list "\$$key" ]
109 elog NOTICE "$key: $val"
118 CREATE TRIGGER show_trigger_data_trig
119 BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
120 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
121 CREATE TRIGGER statement_trigger
122 BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test
123 FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(42,'statement trigger');
125 CREATE TRIGGER show_trigger_data_view_trig
126 INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
127 FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
130 -- Trigger function on every change to T_pkey1
132 create function trig_pkey1_before() returns trigger as E'
134 # Create prepared plans on the first call
136 if {![info exists GD]} {
138 # Plan to check for duplicate key in T_pkey1
140 set GD(plan_pkey1) [spi_prepare \\
141 "select check_pkey1_exists(\\$1, \\$2) as ret" \\
144 # Plan to check for references from T_dta1
146 set GD(plan_dta1) [spi_prepare \\
147 "select 1 from T_dta1 \\
148 where ref1 = \\$1 and ref2 = \\$2" \\
161 # Must check for duplicate key on INSERT
167 # Must check for duplicate key on UPDATE only if
168 # the key changes. In that case we must check for
169 # references to OLD values too.
171 if {[string compare $NEW(key1) $OLD(key1)] != 0} {
175 if {[string compare $NEW(key2) $OLD(key2)] != 0} {
182 # Must only check for references to OLD on DELETE
188 if {$check_new_dup} {
190 # Check for duplicate key
192 spi_execp -count 1 $GD(plan_pkey1) [list $NEW(key1) $NEW(key2)]
195 "duplicate key ''$NEW(key1)'', ''$NEW(key2)'' for T_pkey1"
199 if {$check_old_ref} {
201 # Check for references to OLD
203 set n [spi_execp -count 1 $GD(plan_dta1) [list $OLD(key1) $OLD(key2)]]
206 "key ''$OLD(key1)'', ''$OLD(key2)'' referenced by T_dta1"
211 # Anything is fine - let operation pass through
217 create trigger pkey1_before before insert or update or delete on T_pkey1
218 for each row execute procedure
223 -- Trigger function to check for duplicate keys in T_pkey2
224 -- and to force key2 to be upper case only without leading whitespaces
226 create function trig_pkey2_before() returns trigger as E'
228 # Prepare plan on first call
230 if {![info exists GD]} {
231 set GD(plan_pkey2) [spi_prepare \\
232 "select 1 from T_pkey2 \\
233 where key1 = \\$1 and key2 = \\$2" \\
240 set NEW(key2) [string toupper [string trim $NEW(key2)]]
243 # Check for duplicate key
245 set n [spi_execp -count 1 $GD(plan_pkey2) [list $NEW(key1) $NEW(key2)]]
248 "duplicate key ''$NEW(key1)'', ''$NEW(key2)'' for T_pkey2"
252 # Return modified tuple in NEW
254 return [array get NEW]
258 create trigger pkey2_before before insert or update on T_pkey2
259 for each row execute procedure
264 -- Trigger function to force references from T_dta2 follow changes
265 -- in T_pkey2 or be deleted too. This must be done AFTER the changes
266 -- in T_pkey2 are done so the trigger for primkey check on T_dta2
267 -- fired on our updates will see the new key values in T_pkey2.
269 create function trig_pkey2_after() returns trigger as E'
271 # Prepare plans on first call
273 if {![info exists GD]} {
275 # Plan to update references from T_dta2
277 set GD(plan_dta2_upd) [spi_prepare \\
278 "update T_dta2 set ref1 = \\$3, ref2 = \\$4 \\
279 where ref1 = \\$1 and ref2 = \\$2" \\
280 {int4 bpchar int4 bpchar}]
282 # Plan to delete references from T_dta2
284 set GD(plan_dta2_del) [spi_prepare \\
285 "delete from T_dta2 \\
286 where ref1 = \\$1 and ref2 = \\$2" \\
299 # On update we must let old references follow
301 set NEW(key2) [string toupper $NEW(key2)]
303 if {[string compare $NEW(key1) $OLD(key1)] != 0} {
306 if {[string compare $NEW(key2) $OLD(key2)] != 0} {
312 # On delete we must delete references too
318 if {$old_ref_follow} {
320 # Let old references follow and fire NOTICE message if
323 set n [spi_execp $GD(plan_dta2_upd) \\
324 [list $OLD(key1) $OLD(key2) $NEW(key1) $NEW(key2)]]
327 "updated $n entries in T_dta2 for new key in T_pkey2"
331 if {$old_ref_delete} {
333 # delete references and fire NOTICE message if
336 set n [spi_execp $GD(plan_dta2_del) \\
337 [list $OLD(key1) $OLD(key2)]]
340 "deleted $n entries from T_dta2"
348 create trigger pkey2_after after update or delete on T_pkey2
349 for each row execute procedure
354 -- Generic trigger function to check references in T_dta1 and T_dta2
356 create function check_primkey() returns trigger as E'
358 # For every trigger/relation pair we create
359 # a saved plan and hold them in GD
361 set plankey [list "plan" $TG_name $TG_relid]
362 set planrel [list "relname" $TG_relid]
365 # Extract the pkey relation name
367 set keyidx [expr [llength $args] / 2]
368 set keyrel [string tolower [lindex $args $keyidx]]
370 if {![info exists GD($plankey)]} {
372 # We must prepare a new plan. Build up a query string
373 # for the primary key check.
375 set keylist [lrange $args [expr $keyidx + 1] end]
377 set query "select 1 from $keyrel"
381 foreach key $keylist {
382 set key [string tolower $key]
384 # Add the qual part to the query string
386 append query "$qual $key = \\$$idx"
390 # Lookup the fields type in pg_attribute
392 set n [spi_exec "select T.typname \\
393 from pg_catalog.pg_type T, pg_catalog.pg_attribute A, pg_catalog.pg_class C \\
394 where C.relname = ''[quote $keyrel]'' \\
395 and C.oid = A.attrelid \\
396 and A.attname = ''[quote $key]'' \\
397 and A.atttypid = T.oid"]
399 elog ERROR "table $keyrel doesn''t have a field named $key"
403 # Append the fields type to the argument type list
405 lappend typlist $typname
412 set GD($plankey) [spi_prepare $query $typlist]
415 # Lookup and remember the table name for later error messages
417 spi_exec "select relname from pg_catalog.pg_class \\
418 where oid = ''$TG_relid''::oid"
419 set GD($planrel) $relname
423 # Build the argument list from the NEW row
427 foreach arg [lrange $args 0 $keyidx] {
428 lappend arglist $NEW($arg)
432 # Check for the primary key
434 set n [spi_execp -count 1 $GD($plankey) $arglist]
436 elog ERROR "key for $GD($planrel) not in $keyrel"
446 create trigger dta1_before before insert or update on T_dta1
447 for each row execute procedure
448 check_primkey('ref1', 'ref2', 'T_pkey1', 'key1', 'key2');
451 create trigger dta2_before before insert or update on T_dta2
452 for each row execute procedure
453 check_primkey('ref1', 'ref2', 'T_pkey2', 'key1', 'key2');
456 create function tcl_composite_arg_ref1(T_dta1) returns int as '
460 create function tcl_composite_arg_ref2(T_dta1) returns text as '
464 create function tcl_argisnull(text) returns bool as '
468 create function tcl_lastoid(tabname text) returns int8 as '
469 spi_exec "insert into $1 default values"
474 create function tcl_int4add(int4,int4) returns int4 as '
475 return [expr $1 + $2]
478 -- We use split(n) as a quick-and-dirty way of parsing the input array
479 -- value, which comes in as a string like '{1,2}'. There are better ways...
481 create function tcl_int4_accum(int4[], int4) returns int4[] as '
482 set state [split $1 "{,}"]
483 set newsum [expr {[lindex $state 1] + $2}]
484 set newcnt [expr {[lindex $state 2] + 1}]
485 return "{$newsum,$newcnt}"
488 create function tcl_int4_avg(int4[]) returns int4 as '
489 set state [split $1 "{,}"]
490 if {[lindex $state 2] == 0} { return_null }
491 return [expr {[lindex $state 1] / [lindex $state 2]}]
494 create aggregate tcl_avg (
495 sfunc = tcl_int4_accum,
498 finalfunc = tcl_int4_avg,
502 create aggregate tcl_sum (
509 create function tcl_int4lt(int4,int4) returns bool as '
516 create function tcl_int4le(int4,int4) returns bool as '
523 create function tcl_int4eq(int4,int4) returns bool as '
530 create function tcl_int4ge(int4,int4) returns bool as '
537 create function tcl_int4gt(int4,int4) returns bool as '
547 procedure = tcl_int4lt
550 create operator @<= (
553 procedure = tcl_int4le
559 procedure = tcl_int4eq
562 create operator @>= (
565 procedure = tcl_int4ge
571 procedure = tcl_int4gt
574 create function tcl_int4cmp(int4,int4) returns int4 as '
584 CREATE OPERATOR CLASS tcl_int4_ops
585 FOR TYPE int4 USING btree AS
591 FUNCTION 1 tcl_int4cmp(int4,int4) ;
594 -- Test usage of Tcl's "clock" command. In recent Tcl versions this
595 -- command fails without working "unknown" support, so it's a good canary
596 -- for initialization problems.
598 create function tcl_date_week(int4,int4,int4) returns text as $$
599 return [clock format [clock scan "$2/$3/$1"] -format "%U"]
600 $$ language pltcl immutable;
602 select tcl_date_week(2010,1,24);
603 select tcl_date_week(2001,10,24);
605 -- test pltcl event triggers
606 create function tclsnitch() returns event_trigger language pltcl as $$
607 elog NOTICE "tclsnitch: $TG_event $TG_tag"
610 create event trigger tcl_a_snitch on ddl_command_start execute procedure tclsnitch();
611 create event trigger tcl_b_snitch on ddl_command_end execute procedure tclsnitch();
613 create function foobar() returns int language sql as $$select 1;$$;
614 alter function foobar() cost 77;
615 drop function foobar();
620 drop event trigger tcl_a_snitch;
621 drop event trigger tcl_b_snitch;
623 create function tcl_test_cube_squared(in int, out squared int, out cubed int) as $$
624 return [list squared [expr {$1 * $1}] cubed [expr {$1 * $1 * $1}]]
627 create function tcl_test_squared_rows(int,int) returns table (x int, y int) as $$
628 for {set i $1} {$i < $2} {incr i} {
629 return_next [list y [expr {$i * $i}] x $i]
633 create function tcl_test_sequence(int,int) returns setof int as $$
634 for {set i $1} {$i < $2} {incr i} {
639 create function tcl_eval(string text) returns text as $$
643 -- test use of errorCode in error handling
644 create function tcl_error_handling_test(text) returns text
647 if {[catch $1 err]} {
648 # If not a Postgres error, just return the basic error message
649 if {[lindex $::errorCode 0] != "POSTGRES"} {
653 # Get rid of keys that can't be expected to remain constant
654 array set myArray $::errorCode
655 unset myArray(POSTGRES)
656 unset -nocomplain myArray(funcname)
657 unset -nocomplain myArray(filename)
658 unset -nocomplain myArray(lineno)
660 # Format into something nicer
662 foreach {key} [lsort [array names myArray]] {
663 set value [string map {"\n" "\n\t"} $myArray($key)]
664 lappend vals "$key: $value"
666 return [join $vals "\n"]
672 -- test spi_exec and spi_execp with -array
673 create function tcl_spi_exec(
677 returns void language pltcl AS $function$
678 set query "select * from (values (1,'foo'),(2,'bar'),(3,'baz')) v(col1,col2)"
680 set prep [spi_prepare $query {}]
681 spi_execp -array A $prep {
682 elog NOTICE "col1 $A(col1), col2 $A(col2)"
686 elog NOTICE "action: $2"
698 error "error message"
701 error "should not get here"
706 spi_exec -array A $query {
707 elog NOTICE "col1 $A(col1), col2 $A(col2)"
711 elog NOTICE "action: $2"
723 error "error message"
726 error "should not get here"
731 elog NOTICE "end of function"