Fix xslt_process() to ensure that it inserts a NULL terminator after the
[PostgreSQL.git] / src / pl / tcl / sql / pltcl_setup.sql
blob55ac7e20d55ec91467415ad36553c8622f32f616
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.
13 create table T_pkey1 (
14     key1        int4,
15     key2        char(20),
16     txt         char(40)
19 create table T_pkey2 (
20     key1        int4,
21     key2        char(20),
22     txt         char(40)
25 create table T_dta1 (
26     tkey        char(10),
27     ref1        int4,
28     ref2        char(20)
31 create table T_dta2 (
32     tkey        char(10),
33     ref1        int4,
34     ref2        char(20)
39 -- Function to check key existance 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"              \\
46             {int4 bpchar}]
47     }
48     
49     set n [spi_execp -count 1 $GD(plan) [list $1 $2]]
51     if {$n > 0} {
52         return "t"
53     }
54     return "f"
55 ' language pltcl;
58 -- dump trigger data
60 CREATE TABLE trigger_test
61     (i int, v text );
63 CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$
65         if { [info exists TG_relid] } {
66         set TG_relid "bogus:12345"
67         }
69         set dnames [info locals {[a-zA-Z]*} ]
71         foreach key [lsort $dnames] {
72     
73                 if { [array exists $key] } { 
74                         set str "{"
75                         foreach akey [lsort [ array names $key ] ] {
76                                 if {[string length $str] > 1} { set str "$str, " }
77                                 set cmd "($akey)"
78                                 set cmd "set val \$$key$cmd"
79                                 eval $cmd
80                                 set str "$str$akey: $val"
81                         }
82                         set str "$str}"
83                 elog NOTICE "$key: $str"
84                 } else {
85                         set val [eval list "\$$key" ]
86                 elog NOTICE "$key: $val"
87                 }
88         }
91         return OK  
93 $_$;
95 CREATE TRIGGER show_trigger_data_trig 
96 BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
97 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
101 -- Trigger function on every change to T_pkey1
103 create function trig_pkey1_before() returns trigger as E'
104     #
105     # Create prepared plans on the first call
106     #
107     if {![info exists GD]} {
108         #
109         # Plan to check for duplicate key in T_pkey1
110         #
111         set GD(plan_pkey1) [spi_prepare                         \\
112             "select check_pkey1_exists(\\$1, \\$2) as ret"      \\
113             {int4 bpchar}]
114         #
115         # Plan to check for references from T_dta1
116         #
117         set GD(plan_dta1) [spi_prepare                          \\
118             "select 1 from T_dta1                               \\
119                 where ref1 = \\$1 and ref2 = \\$2"              \\
120             {int4 bpchar}]
121     }
123     #
124     # Initialize flags
125     #
126     set check_old_ref 0
127     set check_new_dup 0
129     switch $TG_op {
130         INSERT {
131             #
132             # Must check for duplicate key on INSERT
133             #
134             set check_new_dup 1
135         }
136         UPDATE {
137             #
138             # Must check for duplicate key on UPDATE only if
139             # the key changes. In that case we must check for
140             # references to OLD values too.
141             #
142             if {[string compare $NEW(key1) $OLD(key1)] != 0} {
143                 set check_old_ref 1
144                 set check_new_dup 1
145             }
146             if {[string compare $NEW(key2) $OLD(key2)] != 0} {
147                 set check_old_ref 1
148                 set check_new_dup 1
149             }
150         }
151         DELETE {
152             #
153             # Must only check for references to OLD on DELETE
154             #
155             set check_old_ref 1
156         }
157     }
159     if {$check_new_dup} {
160         #
161         # Check for duplicate key
162         #
163         spi_execp -count 1 $GD(plan_pkey1) [list $NEW(key1) $NEW(key2)]
164         if {$ret == "t"} {
165             elog ERROR \\
166                 "duplicate key ''$NEW(key1)'', ''$NEW(key2)'' for T_pkey1"
167         }
168     }
170     if {$check_old_ref} {
171         #
172         # Check for references to OLD
173         #
174         set n [spi_execp -count 1 $GD(plan_dta1) [list $OLD(key1) $OLD(key2)]]
175         if {$n > 0} {
176             elog ERROR \\
177                 "key ''$OLD(key1)'', ''$OLD(key2)'' referenced by T_dta1"
178         }
179     }
181     #
182     # Anything is fine - let operation pass through
183     #
184     return OK
185 ' language pltcl;
188 create trigger pkey1_before before insert or update or delete on T_pkey1
189         for each row execute procedure
190         trig_pkey1_before();
194 -- Trigger function to check for duplicate keys in T_pkey2
195 -- and to force key2 to be upper case only without leading whitespaces
197 create function trig_pkey2_before() returns trigger as E'
198     #
199     # Prepare plan on first call
200     #
201     if {![info exists GD]} {
202         set GD(plan_pkey2) [spi_prepare                         \\
203             "select 1 from T_pkey2                              \\
204                 where key1 = \\$1 and key2 = \\$2"              \\
205             {int4 bpchar}]
206     }
208     #
209     # Convert key2 value
210     #
211     set NEW(key2) [string toupper [string trim $NEW(key2)]]
213     #
214     # Check for duplicate key
215     #
216     set n [spi_execp -count 1 $GD(plan_pkey2) [list $NEW(key1) $NEW(key2)]]
217     if {$n > 0} {
218         elog ERROR \\
219             "duplicate key ''$NEW(key1)'', ''$NEW(key2)'' for T_pkey2"
220     }
222     #
223     # Return modified tuple in NEW
224     #
225     return [array get NEW]
226 ' language pltcl;
229 create trigger pkey2_before before insert or update on T_pkey2
230         for each row execute procedure
231         trig_pkey2_before();
235 -- Trigger function to force references from T_dta2 follow changes
236 -- in T_pkey2 or be deleted too. This must be done AFTER the changes
237 -- in T_pkey2 are done so the trigger for primkey check on T_dta2
238 -- fired on our updates will see the new key values in T_pkey2.
240 create function trig_pkey2_after() returns trigger as E'
241     #
242     # Prepare plans on first call
243     #
244     if {![info exists GD]} {
245         #
246         # Plan to update references from T_dta2
247         #
248         set GD(plan_dta2_upd) [spi_prepare                      \\
249             "update T_dta2 set ref1 = \\$3, ref2 = \\$4         \\
250                 where ref1 = \\$1 and ref2 = \\$2"              \\
251             {int4 bpchar int4 bpchar}]
252         #
253         # Plan to delete references from T_dta2
254         #
255         set GD(plan_dta2_del) [spi_prepare                      \\
256             "delete from T_dta2                                 \\
257                 where ref1 = \\$1 and ref2 = \\$2"              \\
258             {int4 bpchar}]
259     }
261     #
262     # Initialize flags
263     #
264     set old_ref_follow 0
265     set old_ref_delete 0
267     switch $TG_op {
268         UPDATE {
269             #
270             # On update we must let old references follow
271             #
272             set NEW(key2) [string toupper $NEW(key2)]
274             if {[string compare $NEW(key1) $OLD(key1)] != 0} {
275                 set old_ref_follow 1
276             }
277             if {[string compare $NEW(key2) $OLD(key2)] != 0} {
278                 set old_ref_follow 1
279             }
280         }
281         DELETE {
282             #
283             # On delete we must delete references too
284             #
285             set old_ref_delete 1
286         }
287     }
289     if {$old_ref_follow} {
290         #
291         # Let old references follow and fire NOTICE message if
292         # there where some
293         #
294         set n [spi_execp $GD(plan_dta2_upd) \\
295             [list $OLD(key1) $OLD(key2) $NEW(key1) $NEW(key2)]]
296         if {$n > 0} {
297             elog NOTICE \\
298                 "updated $n entries in T_dta2 for new key in T_pkey2"
299         }
300     }
302     if {$old_ref_delete} {
303         #
304         # delete references and fire NOTICE message if
305         # there where some
306         #
307         set n [spi_execp $GD(plan_dta2_del) \\
308             [list $OLD(key1) $OLD(key2)]]
309         if {$n > 0} {
310             elog NOTICE \\
311                 "deleted $n entries from T_dta2"
312         }
313     }
315     return OK
316 ' language pltcl;
319 create trigger pkey2_after after update or delete on T_pkey2
320         for each row execute procedure
321         trig_pkey2_after();
325 -- Generic trigger function to check references in T_dta1 and T_dta2
327 create function check_primkey() returns trigger as E'
328     #
329     # For every trigger/relation pair we create
330     # a saved plan and hold them in GD
331     #
332     set plankey [list "plan" $TG_name $TG_relid]
333     set planrel [list "relname" $TG_relid]
335     #
336     # Extract the pkey relation name
337     #
338     set keyidx [expr [llength $args] / 2]
339     set keyrel [string tolower [lindex $args $keyidx]]
341     if {![info exists GD($plankey)]} {
342         #
343         # We must prepare a new plan. Build up a query string
344         # for the primary key check.
345         #
346         set keylist [lrange $args [expr $keyidx + 1] end]
348         set query "select 1 from $keyrel"
349         set qual " where"
350         set typlist ""
351         set idx 1
352         foreach key $keylist {
353             set key [string tolower $key]
354             #
355             # Add the qual part to the query string
356             #
357             append query "$qual $key = \\$$idx"
358             set qual " and"
360             #
361             # Lookup the fields type in pg_attribute
362             #
363             set n [spi_exec "select T.typname                   \\
364                 from pg_catalog.pg_type T, pg_catalog.pg_attribute A, pg_catalog.pg_class C     \\
365                 where C.relname  = ''[quote $keyrel]''          \\
366                   and C.oid      = A.attrelid                   \\
367                   and A.attname  = ''[quote $key]''             \\
368                   and A.atttypid = T.oid"]
369             if {$n != 1} {
370                 elog ERROR "table $keyrel doesn''t have a field named $key"
371             }
373             #
374             # Append the fields type to the argument type list
375             #
376             lappend typlist $typname
377             incr idx
378         }
380         #
381         # Prepare the plan
382         #
383         set GD($plankey) [spi_prepare $query $typlist]
385         #
386         # Lookup and remember the table name for later error messages
387         #
388         spi_exec "select relname from pg_catalog.pg_class       \\
389                 where oid = ''$TG_relid''::oid"
390         set GD($planrel) $relname
391     }
393     #
394     # Build the argument list from the NEW row
395     #
396     incr keyidx -1
397     set arglist ""
398     foreach arg [lrange $args 0 $keyidx] {
399         lappend arglist $NEW($arg)
400     }
402     #
403     # Check for the primary key
404     #
405     set n [spi_execp -count 1 $GD($plankey) $arglist]
406     if {$n <= 0} {
407         elog ERROR "key for $GD($planrel) not in $keyrel"
408     }
410     #
411     # Anything is fine
412     #
413     return OK
414 ' language pltcl;
417 create trigger dta1_before before insert or update on T_dta1
418         for each row execute procedure
419         check_primkey('ref1', 'ref2', 'T_pkey1', 'key1', 'key2');
422 create trigger dta2_before before insert or update on T_dta2
423         for each row execute procedure
424         check_primkey('ref1', 'ref2', 'T_pkey2', 'key1', 'key2');
427 create function tcl_int4add(int4,int4) returns int4 as '
428     return [expr $1 + $2]
429 ' language pltcl;
431 -- We use split(n) as a quick-and-dirty way of parsing the input array
432 -- value, which comes in as a string like '{1,2}'.  There are better ways...
434 create function tcl_int4_accum(int4[], int4) returns int4[] as '
435     set state [split $1 "{,}"]
436     set newsum [expr {[lindex $state 1] + $2}]
437     set newcnt [expr {[lindex $state 2] + 1}]
438     return "{$newsum,$newcnt}"
439 ' language pltcl;
441 create function tcl_int4_avg(int4[]) returns int4 as '
442     set state [split $1 "{,}"]
443     if {[lindex $state 2] == 0} { return_null }
444     return [expr {[lindex $state 1] / [lindex $state 2]}]
445 ' language pltcl;
447 create aggregate tcl_avg (
448                 sfunc = tcl_int4_accum,
449                 basetype = int4,
450                 stype = int4[],
451                 finalfunc = tcl_int4_avg,
452                 initcond = '{0,0}'
453         );
455 create aggregate tcl_sum (
456                 sfunc = tcl_int4add,
457                 basetype = int4,
458                 stype = int4,
459                 initcond1 = 0
460         );
462 create function tcl_int4lt(int4,int4) returns bool as '
463     if {$1 < $2} {
464         return t
465     }
466     return f
467 ' language pltcl;
469 create function tcl_int4le(int4,int4) returns bool as '
470     if {$1 <= $2} {
471         return t
472     }
473     return f
474 ' language pltcl;
476 create function tcl_int4eq(int4,int4) returns bool as '
477     if {$1 == $2} {
478         return t
479     }
480     return f
481 ' language pltcl;
483 create function tcl_int4ge(int4,int4) returns bool as '
484     if {$1 >= $2} {
485         return t
486     }
487     return f
488 ' language pltcl;
490 create function tcl_int4gt(int4,int4) returns bool as '
491     if {$1 > $2} {
492         return t
493     }
494     return f
495 ' language pltcl;
497 create operator @< (
498                 leftarg = int4,
499                 rightarg = int4,
500                 procedure = tcl_int4lt
501         );
503 create operator @<= (
504                 leftarg = int4,
505                 rightarg = int4,
506                 procedure = tcl_int4le
507         );
509 create operator @= (
510                 leftarg = int4,
511                 rightarg = int4,
512                 procedure = tcl_int4eq
513         );
515 create operator @>= (
516                 leftarg = int4,
517                 rightarg = int4,
518                 procedure = tcl_int4ge
519         );
521 create operator @> (
522                 leftarg = int4,
523                 rightarg = int4,
524                 procedure = tcl_int4gt
525         );
527 create function tcl_int4cmp(int4,int4) returns int4 as '
528     if {$1 < $2} {
529         return -1
530     }
531     if {$1 > $2} {
532         return 1
533     }
534     return 0
535 ' language pltcl;
537 CREATE OPERATOR CLASS tcl_int4_ops
538         FOR TYPE int4 USING btree AS
539         OPERATOR 1  @<,
540         OPERATOR 2  @<=,
541         OPERATOR 3  @=,
542         OPERATOR 4  @>=,
543         OPERATOR 5  @>,
544         FUNCTION 1  tcl_int4cmp(int4,int4) ;