globegit-postgresql-plruby 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. data/Changes +121 -0
  2. data/README.markdown +155 -0
  3. data/Rakefile +48 -0
  4. data/docs/plruby.rb +1931 -0
  5. data/ex_trans.sql +33 -0
  6. data/extconf.rb +267 -0
  7. data/plruby.html +1454 -0
  8. data/plruby.rd +1571 -0
  9. data/postgresql-plruby.gemspec +56 -0
  10. data/src/conversions.h +5 -0
  11. data/src/conversions/basic/conversions.h +25 -0
  12. data/src/conversions/basic/extconf.rb +8 -0
  13. data/src/conversions/basic/plruby_basic.c +357 -0
  14. data/src/conversions/bitstring/bitstring.sql +75 -0
  15. data/src/conversions/bitstring/conversions.h +15 -0
  16. data/src/conversions/bitstring/extconf.rb +8 -0
  17. data/src/conversions/bitstring/plruby_bitstring.c +579 -0
  18. data/src/conversions/convcommon.h +129 -0
  19. data/src/conversions/datetime/conversions.h +13 -0
  20. data/src/conversions/datetime/extconf.rb +8 -0
  21. data/src/conversions/datetime/plruby_datetime.c +269 -0
  22. data/src/conversions/geometry/conversions.h +37 -0
  23. data/src/conversions/geometry/extconf.rb +8 -0
  24. data/src/conversions/geometry/geometry.sql +196 -0
  25. data/src/conversions/geometry/plruby_geometry.c +2494 -0
  26. data/src/conversions/network/conversions.h +21 -0
  27. data/src/conversions/network/extconf.rb +8 -0
  28. data/src/conversions/network/network.sql +63 -0
  29. data/src/conversions/network/plruby_network.c +537 -0
  30. data/src/package.h +20 -0
  31. data/src/plpl.c +1708 -0
  32. data/src/plplan.c +893 -0
  33. data/src/plruby.c +1676 -0
  34. data/src/plruby.h +324 -0
  35. data/src/pltrans.c +388 -0
  36. data/test/conv_bitstring/b.rb +45 -0
  37. data/test/conv_bitstring/runtest +26 -0
  38. data/test/conv_bitstring/test.expected.73 +148 -0
  39. data/test/conv_bitstring/test.expected.74 +148 -0
  40. data/test/conv_bitstring/test.expected.80 +148 -0
  41. data/test/conv_bitstring/test.expected.81 +148 -0
  42. data/test/conv_bitstring/test.expected.82 +148 -0
  43. data/test/conv_bitstring/test.expected.83 +148 -0
  44. data/test/conv_bitstring/test.expected.84 +148 -0
  45. data/test/conv_bitstring/test.out +148 -0
  46. data/test/conv_bitstring/test_mklang.sql +8 -0
  47. data/test/conv_bitstring/test_queries.sql +63 -0
  48. data/test/conv_bitstring/test_queries.sql.in +63 -0
  49. data/test/conv_geometry/b.rb +45 -0
  50. data/test/conv_geometry/runtest +26 -0
  51. data/test/conv_geometry/test.expected.73 +265 -0
  52. data/test/conv_geometry/test.expected.74 +265 -0
  53. data/test/conv_geometry/test.expected.80 +265 -0
  54. data/test/conv_geometry/test.expected.81 +265 -0
  55. data/test/conv_geometry/test.expected.82 +265 -0
  56. data/test/conv_geometry/test.expected.83 +265 -0
  57. data/test/conv_geometry/test.expected.84 +265 -0
  58. data/test/conv_geometry/test.out +265 -0
  59. data/test/conv_geometry/test_mklang.sql +8 -0
  60. data/test/conv_geometry/test_queries.sql +194 -0
  61. data/test/conv_geometry/test_queries.sql.in +194 -0
  62. data/test/conv_network/b.rb +45 -0
  63. data/test/conv_network/runtest +26 -0
  64. data/test/conv_network/test.expected.73 +213 -0
  65. data/test/conv_network/test.expected.74 +237 -0
  66. data/test/conv_network/test.expected.80 +237 -0
  67. data/test/conv_network/test.expected.81 +237 -0
  68. data/test/conv_network/test.expected.82 +237 -0
  69. data/test/conv_network/test.expected.83 +237 -0
  70. data/test/conv_network/test.expected.84 +237 -0
  71. data/test/conv_network/test.out +237 -0
  72. data/test/conv_network/test_mklang.sql +8 -0
  73. data/test/conv_network/test_queries.sql +60 -0
  74. data/test/conv_network/test_queries.sql.in +60 -0
  75. data/test/plp/b.rb +34 -0
  76. data/test/plp/runtest +29 -0
  77. data/test/plp/test.expected.73 +472 -0
  78. data/test/plp/test.expected.74 +472 -0
  79. data/test/plp/test.expected.75 +472 -0
  80. data/test/plp/test.expected.80 +472 -0
  81. data/test/plp/test.expected.81 +472 -0
  82. data/test/plp/test.expected.82 +472 -0
  83. data/test/plp/test.expected.83 +472 -0
  84. data/test/plp/test.expected.84 +472 -0
  85. data/test/plp/test.out +472 -0
  86. data/test/plp/test_mklang.sql +8 -0
  87. data/test/plp/test_queries.sql +273 -0
  88. data/test/plp/test_setup.sql +931 -0
  89. data/test/plp/test_setup.sql.in +931 -0
  90. data/test/plt/b.rb +34 -0
  91. data/test/plt/runtest +29 -0
  92. data/test/plt/test.expected.73 +178 -0
  93. data/test/plt/test.expected.74 +178 -0
  94. data/test/plt/test.expected.75 +178 -0
  95. data/test/plt/test.expected.80 +178 -0
  96. data/test/plt/test.expected.81 +178 -0
  97. data/test/plt/test.expected.82 +178 -0
  98. data/test/plt/test.expected.83 +164 -0
  99. data/test/plt/test.expected.84 +168 -0
  100. data/test/plt/test.out +168 -0
  101. data/test/plt/test_mklang.sql +8 -0
  102. data/test/plt/test_queries.sql +72 -0
  103. data/test/plt/test_setup.sql +252 -0
  104. data/test/plt/test_setup.sql.in +252 -0
  105. data/test/range/b.rb +45 -0
  106. data/test/range/runtest +26 -0
  107. data/test/range/test.expected.73 +396 -0
  108. data/test/range/test.expected.73.in +396 -0
  109. data/test/range/test.expected.74 +396 -0
  110. data/test/range/test.expected.74.in +396 -0
  111. data/test/range/test.expected.75 +396 -0
  112. data/test/range/test.expected.75.in +396 -0
  113. data/test/range/test.expected.80 +396 -0
  114. data/test/range/test.expected.81 +397 -0
  115. data/test/range/test.expected.82 +397 -0
  116. data/test/range/test.expected.83 +397 -0
  117. data/test/range/test.expected.84 +399 -0
  118. data/test/range/test.out +399 -0
  119. data/test/range/test_mklang.sql +8 -0
  120. data/test/range/test_queries.sql +249 -0
  121. data/test/range/test_queries.sql.in +249 -0
  122. metadata +207 -0
@@ -0,0 +1,8 @@
1
+
2
+ create function plruby_call_handler() returns language_handler
3
+ as '/home/dberger/Repositories/postgresql-plruby/src/plruby.so'
4
+ language 'C';
5
+
6
+ create trusted procedural language 'plruby'
7
+ handler plruby_call_handler
8
+ lancompiler 'PL/Ruby';
@@ -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
+ );