]> granicus.if.org Git - postgresql/blob - src/pl/tcl/sql/pltcl_setup.sql
a8eaba624316854c589294972c90e94f8b7ef85f
[postgresql] / src / pl / tcl / sql / pltcl_setup.sql
1 --
2 -- Create the tables used in the test queries
3 --
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.
6 --
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.
12 --
13 create table T_pkey1 (
14     key1        int4,
15     key2        char(20),
16     txt         char(40)
17 );
18
19 create table T_pkey2 (
20     key1        int4,
21     key2        char(20),
22     txt         char(40)
23 );
24
25 create table T_dta1 (
26     tkey        char(10),
27     ref1        int4,
28     ref2        char(20)
29 );
30
31 create table T_dta2 (
32     tkey        char(10),
33     ref1        int4,
34     ref2        char(20)
35 );
36
37
38 --
39 -- Function to check key existence in T_pkey1
40 --
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"              \\
46             {int4 bpchar}]
47     }
48
49     set n [spi_execp -count 1 $GD(plan) [list $1 $2]]
50
51     if {$n > 0} {
52         return "t"
53     }
54     return "f"
55 ' language pltcl;
56
57
58 -- dump trigger data
59
60 CREATE TABLE trigger_test (
61         i int,
62         v text,
63         dropme text,
64         test_skip boolean DEFAULT false,
65         test_return_null boolean DEFAULT false,
66         test_argisnull boolean DEFAULT false
67 );
68 -- Make certain dropped attributes are handled correctly
69 ALTER TABLE trigger_test DROP dropme;
70
71 CREATE VIEW trigger_test_view AS SELECT i, v FROM trigger_test;
72
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"} {
75                 # Special case tests
76                 if {$NEW(test_return_null) eq "t" } {
77                         return_null
78                 }
79                 if {$NEW(test_argisnull) eq "t" } {
80                         set should_error [argisnull 1]
81                 }
82                 if {$NEW(test_skip) eq "t" } {
83                         elog NOTICE "SKIPPING OPERATION $TG_op"
84                         return SKIP
85                 }
86         }
87
88         if { [info exists TG_relid] } {
89         set TG_relid "bogus:12345"
90         }
91
92         set dnames [info locals {[a-zA-Z]*} ]
93
94         foreach key [lsort $dnames] {
95
96                 if { [array exists $key] } {
97                         set str "{"
98                         foreach akey [lsort [ array names $key ] ] {
99                                 if {[string length $str] > 1} { set str "$str, " }
100                                 set cmd "($akey)"
101                                 set cmd "set val \$$key$cmd"
102                                 eval $cmd
103                                 set str "$str$akey: $val"
104                         }
105                         set str "$str}"
106                 elog NOTICE "$key: $str"
107                 } else {
108                         set val [eval list "\$$key" ]
109                 elog NOTICE "$key: $val"
110                 }
111         }
112
113
114         return OK
115
116 $_$;
117
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');
124
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');
128
129 --
130 -- Trigger function on every change to T_pkey1
131 --
132 create function trig_pkey1_before() returns trigger as E'
133     #
134     # Create prepared plans on the first call
135     #
136     if {![info exists GD]} {
137         #
138         # Plan to check for duplicate key in T_pkey1
139         #
140         set GD(plan_pkey1) [spi_prepare                         \\
141             "select check_pkey1_exists(\\$1, \\$2) as ret"      \\
142             {int4 bpchar}]
143         #
144         # Plan to check for references from T_dta1
145         #
146         set GD(plan_dta1) [spi_prepare                          \\
147             "select 1 from T_dta1                               \\
148                 where ref1 = \\$1 and ref2 = \\$2"              \\
149             {int4 bpchar}]
150     }
151
152     #
153     # Initialize flags
154     #
155     set check_old_ref 0
156     set check_new_dup 0
157
158     switch $TG_op {
159         INSERT {
160             #
161             # Must check for duplicate key on INSERT
162             #
163             set check_new_dup 1
164         }
165         UPDATE {
166             #
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.
170             #
171             if {[string compare $NEW(key1) $OLD(key1)] != 0} {
172                 set check_old_ref 1
173                 set check_new_dup 1
174             }
175             if {[string compare $NEW(key2) $OLD(key2)] != 0} {
176                 set check_old_ref 1
177                 set check_new_dup 1
178             }
179         }
180         DELETE {
181             #
182             # Must only check for references to OLD on DELETE
183             #
184             set check_old_ref 1
185         }
186     }
187
188     if {$check_new_dup} {
189         #
190         # Check for duplicate key
191         #
192         spi_execp -count 1 $GD(plan_pkey1) [list $NEW(key1) $NEW(key2)]
193         if {$ret == "t"} {
194             elog ERROR \\
195                 "duplicate key ''$NEW(key1)'', ''$NEW(key2)'' for T_pkey1"
196         }
197     }
198
199     if {$check_old_ref} {
200         #
201         # Check for references to OLD
202         #
203         set n [spi_execp -count 1 $GD(plan_dta1) [list $OLD(key1) $OLD(key2)]]
204         if {$n > 0} {
205             elog ERROR \\
206                 "key ''$OLD(key1)'', ''$OLD(key2)'' referenced by T_dta1"
207         }
208     }
209
210     #
211     # Anything is fine - let operation pass through
212     #
213     return OK
214 ' language pltcl;
215
216
217 create trigger pkey1_before before insert or update or delete on T_pkey1
218         for each row execute procedure
219         trig_pkey1_before();
220
221
222 --
223 -- Trigger function to check for duplicate keys in T_pkey2
224 -- and to force key2 to be upper case only without leading whitespaces
225 --
226 create function trig_pkey2_before() returns trigger as E'
227     #
228     # Prepare plan on first call
229     #
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"              \\
234             {int4 bpchar}]
235     }
236
237     #
238     # Convert key2 value
239     #
240     set NEW(key2) [string toupper [string trim $NEW(key2)]]
241
242     #
243     # Check for duplicate key
244     #
245     set n [spi_execp -count 1 $GD(plan_pkey2) [list $NEW(key1) $NEW(key2)]]
246     if {$n > 0} {
247         elog ERROR \\
248             "duplicate key ''$NEW(key1)'', ''$NEW(key2)'' for T_pkey2"
249     }
250
251     #
252     # Return modified tuple in NEW
253     #
254     return [array get NEW]
255 ' language pltcl;
256
257
258 create trigger pkey2_before before insert or update on T_pkey2
259         for each row execute procedure
260         trig_pkey2_before();
261
262
263 --
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.
268 --
269 create function trig_pkey2_after() returns trigger as E'
270     #
271     # Prepare plans on first call
272     #
273     if {![info exists GD]} {
274         #
275         # Plan to update references from T_dta2
276         #
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}]
281         #
282         # Plan to delete references from T_dta2
283         #
284         set GD(plan_dta2_del) [spi_prepare                      \\
285             "delete from T_dta2                                 \\
286                 where ref1 = \\$1 and ref2 = \\$2"              \\
287             {int4 bpchar}]
288     }
289
290     #
291     # Initialize flags
292     #
293     set old_ref_follow 0
294     set old_ref_delete 0
295
296     switch $TG_op {
297         UPDATE {
298             #
299             # On update we must let old references follow
300             #
301             set NEW(key2) [string toupper $NEW(key2)]
302
303             if {[string compare $NEW(key1) $OLD(key1)] != 0} {
304                 set old_ref_follow 1
305             }
306             if {[string compare $NEW(key2) $OLD(key2)] != 0} {
307                 set old_ref_follow 1
308             }
309         }
310         DELETE {
311             #
312             # On delete we must delete references too
313             #
314             set old_ref_delete 1
315         }
316     }
317
318     if {$old_ref_follow} {
319         #
320         # Let old references follow and fire NOTICE message if
321         # there where some
322         #
323         set n [spi_execp $GD(plan_dta2_upd) \\
324             [list $OLD(key1) $OLD(key2) $NEW(key1) $NEW(key2)]]
325         if {$n > 0} {
326             elog NOTICE \\
327                 "updated $n entries in T_dta2 for new key in T_pkey2"
328         }
329     }
330
331     if {$old_ref_delete} {
332         #
333         # delete references and fire NOTICE message if
334         # there where some
335         #
336         set n [spi_execp $GD(plan_dta2_del) \\
337             [list $OLD(key1) $OLD(key2)]]
338         if {$n > 0} {
339             elog NOTICE \\
340                 "deleted $n entries from T_dta2"
341         }
342     }
343
344     return OK
345 ' language pltcl;
346
347
348 create trigger pkey2_after after update or delete on T_pkey2
349         for each row execute procedure
350         trig_pkey2_after();
351
352
353 --
354 -- Generic trigger function to check references in T_dta1 and T_dta2
355 --
356 create function check_primkey() returns trigger as E'
357     #
358     # For every trigger/relation pair we create
359     # a saved plan and hold them in GD
360     #
361     set plankey [list "plan" $TG_name $TG_relid]
362     set planrel [list "relname" $TG_relid]
363
364     #
365     # Extract the pkey relation name
366     #
367     set keyidx [expr [llength $args] / 2]
368     set keyrel [string tolower [lindex $args $keyidx]]
369
370     if {![info exists GD($plankey)]} {
371         #
372         # We must prepare a new plan. Build up a query string
373         # for the primary key check.
374         #
375         set keylist [lrange $args [expr $keyidx + 1] end]
376
377         set query "select 1 from $keyrel"
378         set qual " where"
379         set typlist ""
380         set idx 1
381         foreach key $keylist {
382             set key [string tolower $key]
383             #
384             # Add the qual part to the query string
385             #
386             append query "$qual $key = \\$$idx"
387             set qual " and"
388
389             #
390             # Lookup the fields type in pg_attribute
391             #
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"]
398             if {$n != 1} {
399                 elog ERROR "table $keyrel doesn''t have a field named $key"
400             }
401
402             #
403             # Append the fields type to the argument type list
404             #
405             lappend typlist $typname
406             incr idx
407         }
408
409         #
410         # Prepare the plan
411         #
412         set GD($plankey) [spi_prepare $query $typlist]
413
414         #
415         # Lookup and remember the table name for later error messages
416         #
417         spi_exec "select relname from pg_catalog.pg_class       \\
418                 where oid = ''$TG_relid''::oid"
419         set GD($planrel) $relname
420     }
421
422     #
423     # Build the argument list from the NEW row
424     #
425     incr keyidx -1
426     set arglist ""
427     foreach arg [lrange $args 0 $keyidx] {
428         lappend arglist $NEW($arg)
429     }
430
431     #
432     # Check for the primary key
433     #
434     set n [spi_execp -count 1 $GD($plankey) $arglist]
435     if {$n <= 0} {
436         elog ERROR "key for $GD($planrel) not in $keyrel"
437     }
438
439     #
440     # Anything is fine
441     #
442     return OK
443 ' language pltcl;
444
445
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');
449
450
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');
454
455
456 create function tcl_composite_arg_ref1(T_dta1) returns int as '
457     return $1(ref1)
458 ' language pltcl;
459
460 create function tcl_composite_arg_ref2(T_dta1) returns text as '
461     return $1(ref2)
462 ' language pltcl;
463
464 create function tcl_argisnull(text) returns bool as '
465     argisnull 1
466 ' language pltcl;
467
468 create function tcl_lastoid(tabname text) returns int8 as '
469     spi_exec "insert into $1 default values"
470     spi_lastoid
471 ' language pltcl;
472
473
474 create function tcl_int4add(int4,int4) returns int4 as '
475     return [expr $1 + $2]
476 ' language pltcl;
477
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...
480
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}"
486 ' language pltcl;
487
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]}]
492 ' language pltcl;
493
494 create aggregate tcl_avg (
495                 sfunc = tcl_int4_accum,
496                 basetype = int4,
497                 stype = int4[],
498                 finalfunc = tcl_int4_avg,
499                 initcond = '{0,0}'
500         );
501
502 create aggregate tcl_sum (
503                 sfunc = tcl_int4add,
504                 basetype = int4,
505                 stype = int4,
506                 initcond1 = 0
507         );
508
509 create function tcl_int4lt(int4,int4) returns bool as '
510     if {$1 < $2} {
511         return t
512     }
513     return f
514 ' language pltcl;
515
516 create function tcl_int4le(int4,int4) returns bool as '
517     if {$1 <= $2} {
518         return t
519     }
520     return f
521 ' language pltcl;
522
523 create function tcl_int4eq(int4,int4) returns bool as '
524     if {$1 == $2} {
525         return t
526     }
527     return f
528 ' language pltcl;
529
530 create function tcl_int4ge(int4,int4) returns bool as '
531     if {$1 >= $2} {
532         return t
533     }
534     return f
535 ' language pltcl;
536
537 create function tcl_int4gt(int4,int4) returns bool as '
538     if {$1 > $2} {
539         return t
540     }
541     return f
542 ' language pltcl;
543
544 create operator @< (
545                 leftarg = int4,
546                 rightarg = int4,
547                 procedure = tcl_int4lt
548         );
549
550 create operator @<= (
551                 leftarg = int4,
552                 rightarg = int4,
553                 procedure = tcl_int4le
554         );
555
556 create operator @= (
557                 leftarg = int4,
558                 rightarg = int4,
559                 procedure = tcl_int4eq
560         );
561
562 create operator @>= (
563                 leftarg = int4,
564                 rightarg = int4,
565                 procedure = tcl_int4ge
566         );
567
568 create operator @> (
569                 leftarg = int4,
570                 rightarg = int4,
571                 procedure = tcl_int4gt
572         );
573
574 create function tcl_int4cmp(int4,int4) returns int4 as '
575     if {$1 < $2} {
576         return -1
577     }
578     if {$1 > $2} {
579         return 1
580     }
581     return 0
582 ' language pltcl;
583
584 CREATE OPERATOR CLASS tcl_int4_ops
585         FOR TYPE int4 USING btree AS
586         OPERATOR 1  @<,
587         OPERATOR 2  @<=,
588         OPERATOR 3  @=,
589         OPERATOR 4  @>=,
590         OPERATOR 5  @>,
591         FUNCTION 1  tcl_int4cmp(int4,int4) ;
592
593 --
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.
597 --
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;
601
602 select tcl_date_week(2010,1,24);
603 select tcl_date_week(2001,10,24);
604
605 -- test pltcl event triggers
606 create function tclsnitch() returns event_trigger language pltcl as $$
607   elog NOTICE "tclsnitch: $TG_event $TG_tag"
608 $$;
609
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();
612
613 create function foobar() returns int language sql as $$select 1;$$;
614 alter function foobar() cost 77;
615 drop function foobar();
616
617 create table foo();
618 drop table foo;
619
620 drop event trigger tcl_a_snitch;
621 drop event trigger tcl_b_snitch;
622
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}]]
625 $$ language pltcl;
626
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]
630     }
631 $$ language pltcl;
632
633 create function tcl_test_sequence(int,int) returns setof int as $$
634     for {set i $1} {$i < $2} {incr i} {
635         return_next $i
636     }
637 $$ language pltcl;
638
639 create function tcl_eval(string text) returns text as $$
640     eval $1
641 $$ language pltcl;
642
643 -- test use of errorCode in error handling
644 create function tcl_error_handling_test(text) returns text
645 language pltcl
646 as $function$
647     if {[catch $1 err]} {
648         # If not a Postgres error, just return the basic error message
649         if {[lindex $::errorCode 0] != "POSTGRES"} {
650             return $err
651         }
652
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)
659
660         # Format into something nicer
661         set vals []
662         foreach {key} [lsort [array names myArray]] {
663             set value [string map {"\n" "\n\t"} $myArray($key)]
664             lappend vals "$key: $value"
665         }
666         return [join $vals "\n"]
667     } else {
668         return "no error"
669     }
670 $function$;
671
672 -- test spi_exec and spi_execp with -array
673 create function tcl_spi_exec(
674     prepare boolean,
675     action text
676 )
677 returns void language pltcl AS $function$
678 set query "select * from (values (1,'foo'),(2,'bar'),(3,'baz')) v(col1,col2)"
679 if {$1 == "t"} {
680     set prep [spi_prepare $query {}]
681     spi_execp -array A $prep {
682         elog NOTICE "col1 $A(col1), col2 $A(col2)"
683
684         switch $A(col1) {
685             2 {
686                 elog NOTICE "action: $2"
687                 switch $2 {
688                     break {
689                         break
690                     }
691                     continue {
692                         continue
693                     }
694                     return {
695                         return
696                     }
697                     error {
698                         error "error message"
699                     }
700                 }
701                 error "should not get here"
702             }
703         }
704     }
705 } else {
706     spi_exec -array A $query {
707         elog NOTICE "col1 $A(col1), col2 $A(col2)"
708
709         switch $A(col1) {
710             2 {
711                 elog NOTICE "action: $2"
712                 switch $2 {
713                     break {
714                         break
715                     }
716                     continue {
717                         continue
718                     }
719                     return {
720                         return
721                     }
722                     error {
723                         error "error message"
724                     }
725                 }
726                 error "should not get here"
727             }
728         }
729     }
730 }
731 elog NOTICE "end of function"
732 $function$;