globegit-postgresql-plruby 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Changes +121 -0
- data/README.markdown +155 -0
- data/Rakefile +48 -0
- data/docs/plruby.rb +1931 -0
- data/ex_trans.sql +33 -0
- data/extconf.rb +267 -0
- data/plruby.html +1454 -0
- data/plruby.rd +1571 -0
- data/postgresql-plruby.gemspec +56 -0
- data/src/conversions.h +5 -0
- data/src/conversions/basic/conversions.h +25 -0
- data/src/conversions/basic/extconf.rb +8 -0
- data/src/conversions/basic/plruby_basic.c +357 -0
- data/src/conversions/bitstring/bitstring.sql +75 -0
- data/src/conversions/bitstring/conversions.h +15 -0
- data/src/conversions/bitstring/extconf.rb +8 -0
- data/src/conversions/bitstring/plruby_bitstring.c +579 -0
- data/src/conversions/convcommon.h +129 -0
- data/src/conversions/datetime/conversions.h +13 -0
- data/src/conversions/datetime/extconf.rb +8 -0
- data/src/conversions/datetime/plruby_datetime.c +269 -0
- data/src/conversions/geometry/conversions.h +37 -0
- data/src/conversions/geometry/extconf.rb +8 -0
- data/src/conversions/geometry/geometry.sql +196 -0
- data/src/conversions/geometry/plruby_geometry.c +2494 -0
- data/src/conversions/network/conversions.h +21 -0
- data/src/conversions/network/extconf.rb +8 -0
- data/src/conversions/network/network.sql +63 -0
- data/src/conversions/network/plruby_network.c +537 -0
- data/src/package.h +20 -0
- data/src/plpl.c +1708 -0
- data/src/plplan.c +893 -0
- data/src/plruby.c +1676 -0
- data/src/plruby.h +324 -0
- data/src/pltrans.c +388 -0
- data/test/conv_bitstring/b.rb +45 -0
- data/test/conv_bitstring/runtest +26 -0
- data/test/conv_bitstring/test.expected.73 +148 -0
- data/test/conv_bitstring/test.expected.74 +148 -0
- data/test/conv_bitstring/test.expected.80 +148 -0
- data/test/conv_bitstring/test.expected.81 +148 -0
- data/test/conv_bitstring/test.expected.82 +148 -0
- data/test/conv_bitstring/test.expected.83 +148 -0
- data/test/conv_bitstring/test.expected.84 +148 -0
- data/test/conv_bitstring/test.out +148 -0
- data/test/conv_bitstring/test_mklang.sql +8 -0
- data/test/conv_bitstring/test_queries.sql +63 -0
- data/test/conv_bitstring/test_queries.sql.in +63 -0
- data/test/conv_geometry/b.rb +45 -0
- data/test/conv_geometry/runtest +26 -0
- data/test/conv_geometry/test.expected.73 +265 -0
- data/test/conv_geometry/test.expected.74 +265 -0
- data/test/conv_geometry/test.expected.80 +265 -0
- data/test/conv_geometry/test.expected.81 +265 -0
- data/test/conv_geometry/test.expected.82 +265 -0
- data/test/conv_geometry/test.expected.83 +265 -0
- data/test/conv_geometry/test.expected.84 +265 -0
- data/test/conv_geometry/test.out +265 -0
- data/test/conv_geometry/test_mklang.sql +8 -0
- data/test/conv_geometry/test_queries.sql +194 -0
- data/test/conv_geometry/test_queries.sql.in +194 -0
- data/test/conv_network/b.rb +45 -0
- data/test/conv_network/runtest +26 -0
- data/test/conv_network/test.expected.73 +213 -0
- data/test/conv_network/test.expected.74 +237 -0
- data/test/conv_network/test.expected.80 +237 -0
- data/test/conv_network/test.expected.81 +237 -0
- data/test/conv_network/test.expected.82 +237 -0
- data/test/conv_network/test.expected.83 +237 -0
- data/test/conv_network/test.expected.84 +237 -0
- data/test/conv_network/test.out +237 -0
- data/test/conv_network/test_mklang.sql +8 -0
- data/test/conv_network/test_queries.sql +60 -0
- data/test/conv_network/test_queries.sql.in +60 -0
- data/test/plp/b.rb +34 -0
- data/test/plp/runtest +29 -0
- data/test/plp/test.expected.73 +472 -0
- data/test/plp/test.expected.74 +472 -0
- data/test/plp/test.expected.75 +472 -0
- data/test/plp/test.expected.80 +472 -0
- data/test/plp/test.expected.81 +472 -0
- data/test/plp/test.expected.82 +472 -0
- data/test/plp/test.expected.83 +472 -0
- data/test/plp/test.expected.84 +472 -0
- data/test/plp/test.out +472 -0
- data/test/plp/test_mklang.sql +8 -0
- data/test/plp/test_queries.sql +273 -0
- data/test/plp/test_setup.sql +931 -0
- data/test/plp/test_setup.sql.in +931 -0
- data/test/plt/b.rb +34 -0
- data/test/plt/runtest +29 -0
- data/test/plt/test.expected.73 +178 -0
- data/test/plt/test.expected.74 +178 -0
- data/test/plt/test.expected.75 +178 -0
- data/test/plt/test.expected.80 +178 -0
- data/test/plt/test.expected.81 +178 -0
- data/test/plt/test.expected.82 +178 -0
- data/test/plt/test.expected.83 +164 -0
- data/test/plt/test.expected.84 +168 -0
- data/test/plt/test.out +168 -0
- data/test/plt/test_mklang.sql +8 -0
- data/test/plt/test_queries.sql +72 -0
- data/test/plt/test_setup.sql +252 -0
- data/test/plt/test_setup.sql.in +252 -0
- data/test/range/b.rb +45 -0
- data/test/range/runtest +26 -0
- data/test/range/test.expected.73 +396 -0
- data/test/range/test.expected.73.in +396 -0
- data/test/range/test.expected.74 +396 -0
- data/test/range/test.expected.74.in +396 -0
- data/test/range/test.expected.75 +396 -0
- data/test/range/test.expected.75.in +396 -0
- data/test/range/test.expected.80 +396 -0
- data/test/range/test.expected.81 +397 -0
- data/test/range/test.expected.82 +397 -0
- data/test/range/test.expected.83 +397 -0
- data/test/range/test.expected.84 +399 -0
- data/test/range/test.out +399 -0
- data/test/range/test_mklang.sql +8 -0
- data/test/range/test_queries.sql +249 -0
- data/test/range/test_queries.sql.in +249 -0
- metadata +207 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
|
2
|
+
insert into T_pkey1 values (1, 'key1-1', 'test key');
|
3
|
+
insert into T_pkey1 values (1, 'key1-2', 'test key');
|
4
|
+
insert into T_pkey1 values (1, 'key1-3', 'test key');
|
5
|
+
insert into T_pkey1 values (2, 'key2-1', 'test key');
|
6
|
+
insert into T_pkey1 values (2, 'key2-2', 'test key');
|
7
|
+
insert into T_pkey1 values (2, 'key2-3', 'test key');
|
8
|
+
|
9
|
+
insert into T_pkey2 values (1, 'key1-1', 'test key');
|
10
|
+
insert into T_pkey2 values (1, 'key1-2', 'test key');
|
11
|
+
insert into T_pkey2 values (1, 'key1-3', 'test key');
|
12
|
+
insert into T_pkey2 values (2, 'key2-1', 'test key');
|
13
|
+
insert into T_pkey2 values (2, 'key2-2', 'test key');
|
14
|
+
insert into T_pkey2 values (2, 'key2-3', 'test key');
|
15
|
+
|
16
|
+
select * from T_pkey1;
|
17
|
+
|
18
|
+
-- key2 in T_pkey2 should have upper case only
|
19
|
+
select * from T_pkey2;
|
20
|
+
|
21
|
+
insert into T_pkey1 values (1, 'KEY1-3', 'should work');
|
22
|
+
|
23
|
+
-- Due to the upper case translation in trigger this must fail
|
24
|
+
insert into T_pkey2 values (1, 'KEY1-3', 'should fail');
|
25
|
+
|
26
|
+
insert into T_dta1 values ('trec 1', 1, 'key1-1');
|
27
|
+
insert into T_dta1 values ('trec 2', 1, 'key1-2');
|
28
|
+
insert into T_dta1 values ('trec 3', 1, 'key1-3');
|
29
|
+
|
30
|
+
-- Must fail due to unknown key in T_pkey1
|
31
|
+
insert into T_dta1 values ('trec 4', 1, 'key1-4');
|
32
|
+
|
33
|
+
insert into T_dta2 values ('trec 1', 1, 'KEY1-1');
|
34
|
+
insert into T_dta2 values ('trec 2', 1, 'KEY1-2');
|
35
|
+
insert into T_dta2 values ('trec 3', 1, 'KEY1-3');
|
36
|
+
|
37
|
+
-- Must fail due to unknown key in T_pkey2
|
38
|
+
insert into T_dta2 values ('trec 4', 1, 'KEY1-4');
|
39
|
+
|
40
|
+
select * from T_dta1;
|
41
|
+
|
42
|
+
select * from T_dta2;
|
43
|
+
|
44
|
+
update T_pkey1 set key2 = 'key2-9' where key1 = 2 and key2 = 'key2-1';
|
45
|
+
update T_pkey1 set key2 = 'key1-9' where key1 = 1 and key2 = 'key1-1';
|
46
|
+
delete from T_pkey1 where key1 = 2 and key2 = 'key2-2';
|
47
|
+
delete from T_pkey1 where key1 = 1 and key2 = 'key1-2';
|
48
|
+
|
49
|
+
update T_pkey2 set key2 = 'KEY2-9' where key1 = 2 and key2 = 'KEY2-1';
|
50
|
+
update T_pkey2 set key2 = 'KEY1-9' where key1 = 1 and key2 = 'KEY1-1';
|
51
|
+
delete from T_pkey2 where key1 = 2 and key2 = 'KEY2-2';
|
52
|
+
delete from T_pkey2 where key1 = 1 and key2 = 'KEY1-2';
|
53
|
+
|
54
|
+
select * from T_pkey1;
|
55
|
+
select * from T_pkey2;
|
56
|
+
select * from T_dta1;
|
57
|
+
select * from T_dta2;
|
58
|
+
|
59
|
+
select ruby_avg(key1) from T_pkey1;
|
60
|
+
select ruby_sum(key1) from T_pkey1;
|
61
|
+
select ruby_avg(key1) from T_pkey2;
|
62
|
+
select ruby_sum(key1) from T_pkey2;
|
63
|
+
|
64
|
+
-- The following should return NULL instead of 0
|
65
|
+
select ruby_avg(key1) from T_pkey1 where key1 = 99;
|
66
|
+
select ruby_sum(key1) from T_pkey1 where key1 = 99;
|
67
|
+
|
68
|
+
select 1 @< 2;
|
69
|
+
select 100 @< 4;
|
70
|
+
|
71
|
+
select * from T_pkey1 order by key1 using @<;
|
72
|
+
select * from T_pkey2 order by key1 using @<;
|
@@ -0,0 +1,252 @@
|
|
1
|
+
create table T_pkey1 (
|
2
|
+
key1 int4,
|
3
|
+
key2 varchar(20),
|
4
|
+
txt varchar(40)
|
5
|
+
);
|
6
|
+
|
7
|
+
create table T_pkey2 (
|
8
|
+
key1 int4,
|
9
|
+
key2 varchar(20),
|
10
|
+
txt varchar(40)
|
11
|
+
);
|
12
|
+
|
13
|
+
create table T_dta1 (
|
14
|
+
tkey varchar(20),
|
15
|
+
ref1 int4,
|
16
|
+
ref2 varchar(20)
|
17
|
+
);
|
18
|
+
|
19
|
+
create table T_dta2 (
|
20
|
+
tkey varchar(20),
|
21
|
+
ref1 int4,
|
22
|
+
ref2 varchar(20)
|
23
|
+
);
|
24
|
+
|
25
|
+
--
|
26
|
+
-- Function to check key existance in T_pkey1
|
27
|
+
--
|
28
|
+
create function check_pkey1_exists(int4, varchar) returns bool as '
|
29
|
+
if ! $Plans.key?("plan")
|
30
|
+
$Plans["plan"] = PL::Plan.new("select 1 from T_pkey1
|
31
|
+
where key1 = $1 and key2 = $2",
|
32
|
+
["int4", "varchar"]).save
|
33
|
+
end
|
34
|
+
if $Plans["plan"].exec(args, 1)
|
35
|
+
return true
|
36
|
+
else
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
' language 'plruby';
|
40
|
+
|
41
|
+
|
42
|
+
--
|
43
|
+
-- Trigger function on every change to T_pkey1
|
44
|
+
--
|
45
|
+
create function trig_pkey1_before() returns trigger as '
|
46
|
+
if ! $Plans.key?("plan_pkey1")
|
47
|
+
$Plans["plan_pkey1"] = PL::Plan.new("select check_pkey1_exists($1, $2) as ret",
|
48
|
+
["int4", "varchar"]).save
|
49
|
+
$Plans["plan_dta1"] = PL::Plan.new("select 1 from T_dta1
|
50
|
+
where ref1 = $1 and ref2 = $2",
|
51
|
+
["int4", "varchar"]).save
|
52
|
+
end
|
53
|
+
check_old_ref = false
|
54
|
+
check_new_dup = false
|
55
|
+
|
56
|
+
case tg["op"]
|
57
|
+
when PL::INSERT
|
58
|
+
check_new_dup = true
|
59
|
+
when PL::UPDATE
|
60
|
+
check_old_ref = new["key1"] != old["key1"] || new["key2"] != old["key2"]
|
61
|
+
check_new_dup = true
|
62
|
+
when PL::DELETE
|
63
|
+
check_old_ref = true
|
64
|
+
end
|
65
|
+
if check_new_dup
|
66
|
+
n = $Plans["plan_pkey1"].exec([new["key1"], new["key2"]], 1)
|
67
|
+
if n["ret"] == "t"
|
68
|
+
raise "duplicate key ''#{new[''key1'']}'', ''#{new[''key2'']}'' for T_pkey1"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if check_old_ref
|
73
|
+
if $Plans["plan_dta1"].exec([old["key1"], old["key2"]], 1)
|
74
|
+
raise "key ''#{old[''key1'']}'', ''#{old[''key2'']}'' referenced by T_dta1"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
PL::OK
|
78
|
+
' language 'plruby';
|
79
|
+
|
80
|
+
create trigger pkey1_before before insert or update or delete on T_pkey1
|
81
|
+
for each row execute procedure
|
82
|
+
trig_pkey1_before();
|
83
|
+
|
84
|
+
|
85
|
+
--
|
86
|
+
-- Trigger function to check for duplicate keys in T_pkey2
|
87
|
+
-- and to force key2 to be upper case only without leading whitespaces
|
88
|
+
--
|
89
|
+
create function trig_pkey2_before() returns trigger as '
|
90
|
+
if ! $Plans.key?("plan_pkey2")
|
91
|
+
$Plans["plan_pkey2"] = PL::Plan.new("select 1 from T_pkey2
|
92
|
+
where key1 = $1 and key2 = $2",
|
93
|
+
["int4", "varchar"]).save
|
94
|
+
end
|
95
|
+
new["key2"] = new["key2"].sub(/^\\s*/, "").sub(/\\s*$/, "").upcase
|
96
|
+
if $Plans["plan_pkey2"].exec([new["key1"], new["key2"]], 1)
|
97
|
+
raise "duplicate key ''#{new[''key1'']}'', ''#{new[''key2'']}'' for T_pkey2"
|
98
|
+
end
|
99
|
+
new
|
100
|
+
' language 'plruby';
|
101
|
+
|
102
|
+
create trigger pkey2_before before insert or update on T_pkey2
|
103
|
+
for each row execute procedure
|
104
|
+
trig_pkey2_before();
|
105
|
+
|
106
|
+
|
107
|
+
--
|
108
|
+
-- Trigger function to force references from T_dta2 follow changes
|
109
|
+
-- in T_pkey2 or be deleted too. This must be done AFTER the changes
|
110
|
+
-- in T_pkey2 are done so the trigger for primkey check on T_dta2
|
111
|
+
-- fired on our updates will see the new key values in T_pkey2.
|
112
|
+
--
|
113
|
+
create function trig_pkey2_after() returns trigger as '
|
114
|
+
if ! $Plans["plan_dta2_upd"]
|
115
|
+
$Plans["plan_dta2_upd"] =
|
116
|
+
PL::Plan.new("update T_dta2
|
117
|
+
set ref1 = $3, ref2 = $4
|
118
|
+
where ref1 = $1 and ref2 = $2",
|
119
|
+
["int4", "varchar", "int4", "varchar" ]).save
|
120
|
+
$Plans["plan_dta2_del"] =
|
121
|
+
PL::Plan.new("delete from T_dta2
|
122
|
+
where ref1 = $1 and ref2 = $2",
|
123
|
+
["int4", "varchar"]).save
|
124
|
+
end
|
125
|
+
|
126
|
+
old_ref_follow = false
|
127
|
+
old_ref_delete = false
|
128
|
+
|
129
|
+
case tg["op"]
|
130
|
+
when PL::UPDATE
|
131
|
+
new["key2"] = new["key2"].upcase
|
132
|
+
old_ref_follow = (new["key1"] != old["key1"]) ||
|
133
|
+
(new["key2"] != old["key2"])
|
134
|
+
when PL::DELETE
|
135
|
+
old_ref_delete = true
|
136
|
+
end
|
137
|
+
|
138
|
+
if old_ref_follow
|
139
|
+
n = $Plans["plan_dta2_upd"].exec([old["key1"], old["key2"], new["key1"], new["key2"]])
|
140
|
+
warn "updated #{n} entries in T_dta2 for new key in T_pkey2" if n != 0
|
141
|
+
end
|
142
|
+
|
143
|
+
if old_ref_delete
|
144
|
+
n = $Plans["plan_dta2_del"].exec([old["key1"], old["key2"]])
|
145
|
+
warn "deleted #{n} entries from T_dta2" if n != 0
|
146
|
+
end
|
147
|
+
|
148
|
+
PL::OK
|
149
|
+
' language 'plruby';
|
150
|
+
|
151
|
+
|
152
|
+
create trigger pkey2_after after update or delete on T_pkey2
|
153
|
+
for each row execute procedure
|
154
|
+
trig_pkey2_after();
|
155
|
+
|
156
|
+
|
157
|
+
--
|
158
|
+
-- Generic trigger function to check references in T_dta1 and T_dta2
|
159
|
+
--
|
160
|
+
create function check_primkey() returns trigger as '
|
161
|
+
plankey = ["plan", tg["name"], tg["relid"]]
|
162
|
+
planrel = ["relname", tg["relid"]]
|
163
|
+
keyidx = args.size / 2
|
164
|
+
keyrel = args[keyidx].downcase
|
165
|
+
if ! $Plans[plankey]
|
166
|
+
keylist = args[keyidx + 1 .. -1]
|
167
|
+
query = "select 1 from #{keyrel}"
|
168
|
+
qual = " where"
|
169
|
+
typlist = []
|
170
|
+
idx = 1
|
171
|
+
keylist.each do |key|
|
172
|
+
key = key.downcase
|
173
|
+
query << "#{qual} #{key} = $#{idx}"
|
174
|
+
qual = " and"
|
175
|
+
n = PL.exec("select T.typname as typname
|
176
|
+
from pg_type T, pg_attribute A, pg_class C
|
177
|
+
where C.relname = ''#{PL.quote(keyrel)}''
|
178
|
+
and C.oid = A.attrelid
|
179
|
+
and A.attname = ''#{PL.quote(key)}''
|
180
|
+
and A.atttypid = T.oid", 1)
|
181
|
+
if ! n
|
182
|
+
raise "table #{keyrel} doesn''t have a field named #{key}"
|
183
|
+
end
|
184
|
+
typlist.push(n["typname"])
|
185
|
+
idx += 1
|
186
|
+
end
|
187
|
+
$Plans[plankey] = PL::Plan.new(query, typlist).save
|
188
|
+
$Plans[planrel] = PL.exec("select relname from pg_class
|
189
|
+
where oid = ''#{tg[''relid'']}''::oid", 1)
|
190
|
+
end
|
191
|
+
values = []
|
192
|
+
keyidx.times {|x| values.push(new[args[x]]) }
|
193
|
+
n = $Plans[plankey].exec(values, 1)
|
194
|
+
if ! n
|
195
|
+
raise "key for #{$Plans[planrel][''relname'']} not in #{keyrel}"
|
196
|
+
end
|
197
|
+
PL::OK
|
198
|
+
' language 'plruby';
|
199
|
+
|
200
|
+
|
201
|
+
create trigger dta1_before before insert or update on T_dta1
|
202
|
+
for each row execute procedure
|
203
|
+
check_primkey('ref1', 'ref2', 'T_pkey1', 'key1', 'key2');
|
204
|
+
|
205
|
+
|
206
|
+
create trigger dta2_before before insert or update on T_dta2
|
207
|
+
for each row execute procedure
|
208
|
+
check_primkey('ref1', 'ref2', 'T_pkey2', 'key1', 'key2');
|
209
|
+
|
210
|
+
|
211
|
+
create function ruby_int4add(int4,int4) returns int4 as '
|
212
|
+
args[0].to_i + args[1].to_i
|
213
|
+
' language 'plruby';
|
214
|
+
|
215
|
+
create function ruby_int4_accum(_int4, int4) returns _int4 as '
|
216
|
+
a = args[0]
|
217
|
+
[a[0].to_i + args[1].to_i, a[1].to_i + 1]
|
218
|
+
' language 'plruby';
|
219
|
+
|
220
|
+
create function ruby_int4_avg(_int4) returns int4 as '
|
221
|
+
a = args[0]
|
222
|
+
if a[1].to_i == 0
|
223
|
+
nil
|
224
|
+
else
|
225
|
+
a[0].to_i / a[1].to_i
|
226
|
+
end
|
227
|
+
' language 'plruby';
|
228
|
+
|
229
|
+
create aggregate ruby_avg (
|
230
|
+
sfunc = ruby_int4_accum,
|
231
|
+
basetype = int4,
|
232
|
+
stype = _int4,
|
233
|
+
finalfunc = ruby_int4_avg,
|
234
|
+
initcond = '{0,0}'
|
235
|
+
);
|
236
|
+
|
237
|
+
create aggregate ruby_sum (
|
238
|
+
sfunc = ruby_int4add,
|
239
|
+
basetype = int4,
|
240
|
+
stype = int4,
|
241
|
+
initcond = '0'
|
242
|
+
);
|
243
|
+
|
244
|
+
create function ruby_int4lt(int4,int4) returns bool as '
|
245
|
+
args[0].to_i < args[1].to_i
|
246
|
+
' language 'plruby';
|
247
|
+
|
248
|
+
create operator @< (
|
249
|
+
leftarg = int4,
|
250
|
+
rightarg = int4,
|
251
|
+
procedure = ruby_int4lt
|
252
|
+
);
|
@@ -0,0 +1,252 @@
|
|
1
|
+
create table T_pkey1 (
|
2
|
+
key1 int4,
|
3
|
+
key2 varchar(20),
|
4
|
+
txt varchar(40)
|
5
|
+
);
|
6
|
+
|
7
|
+
create table T_pkey2 (
|
8
|
+
key1 int4,
|
9
|
+
key2 varchar(20),
|
10
|
+
txt varchar(40)
|
11
|
+
);
|
12
|
+
|
13
|
+
create table T_dta1 (
|
14
|
+
tkey varchar(20),
|
15
|
+
ref1 int4,
|
16
|
+
ref2 varchar(20)
|
17
|
+
);
|
18
|
+
|
19
|
+
create table T_dta2 (
|
20
|
+
tkey varchar(20),
|
21
|
+
ref1 int4,
|
22
|
+
ref2 varchar(20)
|
23
|
+
);
|
24
|
+
|
25
|
+
--
|
26
|
+
-- Function to check key existance in T_pkey1
|
27
|
+
--
|
28
|
+
create function check_pkey1_exists(int4, varchar) returns bool as '
|
29
|
+
if ! $Plans.key?("plan")
|
30
|
+
$Plans["plan"] = PL::Plan.new("select 1 from T_pkey1
|
31
|
+
where key1 = $1 and key2 = $2",
|
32
|
+
["int4", "varchar"]).save
|
33
|
+
end
|
34
|
+
if $Plans["plan"].exec(args, 1)
|
35
|
+
return true
|
36
|
+
else
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
' language 'plruby';
|
40
|
+
|
41
|
+
|
42
|
+
--
|
43
|
+
-- Trigger function on every change to T_pkey1
|
44
|
+
--
|
45
|
+
create function trig_pkey1_before() returns trigger as '
|
46
|
+
if ! $Plans.key?("plan_pkey1")
|
47
|
+
$Plans["plan_pkey1"] = PL::Plan.new("select check_pkey1_exists($1, $2) as ret",
|
48
|
+
["int4", "varchar"]).save
|
49
|
+
$Plans["plan_dta1"] = PL::Plan.new("select 1 from T_dta1
|
50
|
+
where ref1 = $1 and ref2 = $2",
|
51
|
+
["int4", "varchar"]).save
|
52
|
+
end
|
53
|
+
check_old_ref = false
|
54
|
+
check_new_dup = false
|
55
|
+
|
56
|
+
case tg["op"]
|
57
|
+
when PL::INSERT
|
58
|
+
check_new_dup = true
|
59
|
+
when PL::UPDATE
|
60
|
+
check_old_ref = new["key1"] != old["key1"] || new["key2"] != old["key2"]
|
61
|
+
check_new_dup = true
|
62
|
+
when PL::DELETE
|
63
|
+
check_old_ref = true
|
64
|
+
end
|
65
|
+
if check_new_dup
|
66
|
+
n = $Plans["plan_pkey1"].exec([new["key1"], new["key2"]], 1)
|
67
|
+
if n["ret"] == "t"
|
68
|
+
raise "duplicate key ''#{new[''key1'']}'', ''#{new[''key2'']}'' for T_pkey1"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if check_old_ref
|
73
|
+
if $Plans["plan_dta1"].exec([old["key1"], old["key2"]], 1)
|
74
|
+
raise "key ''#{old[''key1'']}'', ''#{old[''key2'']}'' referenced by T_dta1"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
PL::OK
|
78
|
+
' language 'plruby';
|
79
|
+
|
80
|
+
create trigger pkey1_before before insert or update or delete on T_pkey1
|
81
|
+
for each row execute procedure
|
82
|
+
trig_pkey1_before();
|
83
|
+
|
84
|
+
|
85
|
+
--
|
86
|
+
-- Trigger function to check for duplicate keys in T_pkey2
|
87
|
+
-- and to force key2 to be upper case only without leading whitespaces
|
88
|
+
--
|
89
|
+
create function trig_pkey2_before() returns trigger as '
|
90
|
+
if ! $Plans.key?("plan_pkey2")
|
91
|
+
$Plans["plan_pkey2"] = PL::Plan.new("select 1 from T_pkey2
|
92
|
+
where key1 = $1 and key2 = $2",
|
93
|
+
["int4", "varchar"]).save
|
94
|
+
end
|
95
|
+
new["key2"] = new["key2"].sub(/^\\s*/, "").sub(/\\s*$/, "").upcase
|
96
|
+
if $Plans["plan_pkey2"].exec([new["key1"], new["key2"]], 1)
|
97
|
+
raise "duplicate key ''#{new[''key1'']}'', ''#{new[''key2'']}'' for T_pkey2"
|
98
|
+
end
|
99
|
+
new
|
100
|
+
' language 'plruby';
|
101
|
+
|
102
|
+
create trigger pkey2_before before insert or update on T_pkey2
|
103
|
+
for each row execute procedure
|
104
|
+
trig_pkey2_before();
|
105
|
+
|
106
|
+
|
107
|
+
--
|
108
|
+
-- Trigger function to force references from T_dta2 follow changes
|
109
|
+
-- in T_pkey2 or be deleted too. This must be done AFTER the changes
|
110
|
+
-- in T_pkey2 are done so the trigger for primkey check on T_dta2
|
111
|
+
-- fired on our updates will see the new key values in T_pkey2.
|
112
|
+
--
|
113
|
+
create function trig_pkey2_after() returns trigger as '
|
114
|
+
if ! $Plans["plan_dta2_upd"]
|
115
|
+
$Plans["plan_dta2_upd"] =
|
116
|
+
PL::Plan.new("update T_dta2
|
117
|
+
set ref1 = $3, ref2 = $4
|
118
|
+
where ref1 = $1 and ref2 = $2",
|
119
|
+
["int4", "varchar", "int4", "varchar" ]).save
|
120
|
+
$Plans["plan_dta2_del"] =
|
121
|
+
PL::Plan.new("delete from T_dta2
|
122
|
+
where ref1 = $1 and ref2 = $2",
|
123
|
+
["int4", "varchar"]).save
|
124
|
+
end
|
125
|
+
|
126
|
+
old_ref_follow = false
|
127
|
+
old_ref_delete = false
|
128
|
+
|
129
|
+
case tg["op"]
|
130
|
+
when PL::UPDATE
|
131
|
+
new["key2"] = new["key2"].upcase
|
132
|
+
old_ref_follow = (new["key1"] != old["key1"]) ||
|
133
|
+
(new["key2"] != old["key2"])
|
134
|
+
when PL::DELETE
|
135
|
+
old_ref_delete = true
|
136
|
+
end
|
137
|
+
|
138
|
+
if old_ref_follow
|
139
|
+
n = $Plans["plan_dta2_upd"].exec([old["key1"], old["key2"], new["key1"], new["key2"]])
|
140
|
+
warn "updated #{n} entries in T_dta2 for new key in T_pkey2" if n != 0
|
141
|
+
end
|
142
|
+
|
143
|
+
if old_ref_delete
|
144
|
+
n = $Plans["plan_dta2_del"].exec([old["key1"], old["key2"]])
|
145
|
+
warn "deleted #{n} entries from T_dta2" if n != 0
|
146
|
+
end
|
147
|
+
|
148
|
+
PL::OK
|
149
|
+
' language 'plruby';
|
150
|
+
|
151
|
+
|
152
|
+
create trigger pkey2_after after update or delete on T_pkey2
|
153
|
+
for each row execute procedure
|
154
|
+
trig_pkey2_after();
|
155
|
+
|
156
|
+
|
157
|
+
--
|
158
|
+
-- Generic trigger function to check references in T_dta1 and T_dta2
|
159
|
+
--
|
160
|
+
create function check_primkey() returns trigger as '
|
161
|
+
plankey = ["plan", tg["name"], tg["relid"]]
|
162
|
+
planrel = ["relname", tg["relid"]]
|
163
|
+
keyidx = args.size / 2
|
164
|
+
keyrel = args[keyidx].downcase
|
165
|
+
if ! $Plans[plankey]
|
166
|
+
keylist = args[keyidx + 1 .. -1]
|
167
|
+
query = "select 1 from #{keyrel}"
|
168
|
+
qual = " where"
|
169
|
+
typlist = []
|
170
|
+
idx = 1
|
171
|
+
keylist.each do |key|
|
172
|
+
key = key.downcase
|
173
|
+
query << "#{qual} #{key} = $#{idx}"
|
174
|
+
qual = " and"
|
175
|
+
n = PL.exec("select T.typname as typname
|
176
|
+
from pg_type T, pg_attribute A, pg_class C
|
177
|
+
where C.relname = ''#{PL.quote(keyrel)}''
|
178
|
+
and C.oid = A.attrelid
|
179
|
+
and A.attname = ''#{PL.quote(key)}''
|
180
|
+
and A.atttypid = T.oid", 1)
|
181
|
+
if ! n
|
182
|
+
raise "table #{keyrel} doesn''t have a field named #{key}"
|
183
|
+
end
|
184
|
+
typlist.push(n["typname"])
|
185
|
+
idx += 1
|
186
|
+
end
|
187
|
+
$Plans[plankey] = PL::Plan.new(query, typlist).save
|
188
|
+
$Plans[planrel] = PL.exec("select relname from pg_class
|
189
|
+
where oid = ''#{tg[''relid'']}''::oid", 1)
|
190
|
+
end
|
191
|
+
values = []
|
192
|
+
keyidx.times {|x| values.push(new[args[x]]) }
|
193
|
+
n = $Plans[plankey].exec(values, 1)
|
194
|
+
if ! n
|
195
|
+
raise "key for #{$Plans[planrel][''relname'']} not in #{keyrel}"
|
196
|
+
end
|
197
|
+
PL::OK
|
198
|
+
' language 'plruby';
|
199
|
+
|
200
|
+
|
201
|
+
create trigger dta1_before before insert or update on T_dta1
|
202
|
+
for each row execute procedure
|
203
|
+
check_primkey('ref1', 'ref2', 'T_pkey1', 'key1', 'key2');
|
204
|
+
|
205
|
+
|
206
|
+
create trigger dta2_before before insert or update on T_dta2
|
207
|
+
for each row execute procedure
|
208
|
+
check_primkey('ref1', 'ref2', 'T_pkey2', 'key1', 'key2');
|
209
|
+
|
210
|
+
|
211
|
+
create function ruby_int4add(int4,int4) returns int4 as '
|
212
|
+
args[0].to_i + args[1].to_i
|
213
|
+
' language 'plruby';
|
214
|
+
|
215
|
+
create function ruby_int4_accum(_int4, int4) returns _int4 as '
|
216
|
+
a = args[0]
|
217
|
+
[a[0].to_i + args[1].to_i, a[1].to_i + 1]
|
218
|
+
' language 'plruby';
|
219
|
+
|
220
|
+
create function ruby_int4_avg(_int4) returns int4 as '
|
221
|
+
a = args[0]
|
222
|
+
if a[1].to_i == 0
|
223
|
+
nil
|
224
|
+
else
|
225
|
+
a[0].to_i / a[1].to_i
|
226
|
+
end
|
227
|
+
' language 'plruby';
|
228
|
+
|
229
|
+
create aggregate ruby_avg (
|
230
|
+
sfunc = ruby_int4_accum,
|
231
|
+
basetype = int4,
|
232
|
+
stype = _int4,
|
233
|
+
finalfunc = ruby_int4_avg,
|
234
|
+
initcond = '{0,0}'
|
235
|
+
);
|
236
|
+
|
237
|
+
create aggregate ruby_sum (
|
238
|
+
sfunc = ruby_int4add,
|
239
|
+
basetype = int4,
|
240
|
+
stype = int4,
|
241
|
+
initcond = '0'
|
242
|
+
);
|
243
|
+
|
244
|
+
create function ruby_int4lt(int4,int4) returns bool as '
|
245
|
+
args[0].to_i < args[1].to_i
|
246
|
+
' language 'plruby';
|
247
|
+
|
248
|
+
create operator @< (
|
249
|
+
leftarg = int4,
|
250
|
+
rightarg = int4,
|
251
|
+
procedure = ruby_int4lt
|
252
|
+
);
|