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.
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
+ );