globegit-postgresql-plruby 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
);
|