sequel_postgresql_triggers 1.4.0 → 1.6.0

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.
@@ -1,8 +1,21 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'
3
2
  require 'sequel'
4
- require 'minitest/spec'
5
- require 'minitest/autorun'
3
+
4
+ if coverage = ENV.delete('COVERAGE')
5
+ require 'simplecov'
6
+
7
+ SimpleCov.start do
8
+ enable_coverage :branch
9
+ command_name coverage
10
+ add_filter "/spec/"
11
+ add_group('Missing'){|src| src.covered_percent < 100}
12
+ add_group('Covered'){|src| src.covered_percent == 100}
13
+ end
14
+ end
15
+
16
+ ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
17
+ gem 'minitest'
18
+ require 'minitest/global_expectations/autorun'
6
19
 
7
20
  DB = Sequel.connect(ENV['PGT_SPEC_DB']||'postgres:///spgt_test?user=postgres')
8
21
 
@@ -16,578 +29,687 @@ else
16
29
  end
17
30
  DB.extension :pg_array
18
31
 
19
- describe "PostgreSQL Triggers" do
32
+ describe "PostgreSQL Counter Cache Trigger" do
33
+ before do
34
+ DB.create_table(:accounts){integer :id; integer :num_entries, :default=>0}
35
+ DB.create_table(:entries){integer :id; integer :account_id}
36
+ DB.pgt_counter_cache(:accounts, :id, :num_entries, :entries, :account_id, :function_name=>:spgt_counter_cache)
37
+ DB[:accounts].insert(:id=>1)
38
+ DB[:accounts].insert(:id=>2)
39
+ end
40
+
41
+ after do
42
+ DB.drop_table(:entries, :accounts)
43
+ DB.drop_function(:spgt_counter_cache)
44
+ end
45
+
46
+ it "should not modify counter cache if adding/removing records fails due to ON CONFLICT DO NOTHING" do
47
+ DB.alter_table(:entries){add_unique_constraint :account_id}
48
+ DB[:entries].insert(:id=>1, :account_id=>1)
49
+ DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 0]
50
+ DB[:entries].insert_conflict.insert(:id=>1, :account_id=>1)
51
+ DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 0]
52
+ end
53
+
54
+ it "should modify counter cache when adding or removing records" do
55
+ DB[:accounts].order(:id).select_map(:num_entries).must_equal [0, 0]
56
+
57
+ DB[:entries].insert(:id=>1, :account_id=>1)
58
+ DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 0]
59
+
60
+ DB[:entries].insert(:id=>2, :account_id=>1)
61
+ DB[:accounts].order(:id).select_map(:num_entries).must_equal [2, 0]
62
+
63
+ DB[:entries].insert(:id=>3, :account_id=>nil)
64
+ DB[:accounts].order(:id).select_map(:num_entries).must_equal [2, 0]
65
+
66
+ DB[:entries].where(:id=>3).update(:account_id=>2)
67
+ DB[:accounts].order(:id).select_map(:num_entries).must_equal [2, 1]
68
+
69
+ DB[:entries].where(:id=>2).update(:account_id=>2)
70
+ DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 2]
71
+
72
+ DB[:entries].where(:id=>2).update(:account_id=>nil)
73
+ DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
74
+
75
+ DB[:entries].where(:id=>2).update(:id=>4)
76
+ DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
77
+
78
+ DB[:entries].where(:id=>4).update(:account_id=>2)
79
+ DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 2]
80
+
81
+ DB[:entries].where(:id=>4).update(:account_id=>nil)
82
+ DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
83
+
84
+ DB[:entries].filter(:id=>4).delete
85
+ DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
86
+
87
+ DB[:entries].delete
88
+ DB[:accounts].order(:id).select_map(:num_entries).must_equal [0, 0]
89
+ end
90
+ end
91
+
92
+ describe "PostgreSQL Created At Trigger" do
93
+ before do
94
+ DB.create_table(:accounts){integer :id; timestamp :added_on}
95
+ DB.pgt_created_at(:accounts, :added_on, :function_name=>:spgt_created_at)
96
+ end
97
+
98
+ after do
99
+ DB.drop_table(:accounts)
100
+ DB.drop_function(:spgt_created_at)
101
+ end
102
+
103
+ it "should set the column upon insertion and ignore modifications afterward" do
104
+ DB[:accounts].insert(:id=>1)
105
+ t = DB[:accounts].get(:added_on)
106
+ t.strftime('%F').must_equal Date.today.strftime('%F')
107
+ DB[:accounts].update(:added_on=>Date.today - 60)
108
+ DB[:accounts].get(:added_on).must_equal t
109
+ DB[:accounts].insert(:id=>2)
110
+ ds = DB[:accounts].select(:added_on)
111
+ DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>1)).as(:x)).first[:x].must_equal true
112
+ DB[:accounts].filter(:id=>1).update(:id=>3)
113
+ DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>3)).as(:x)).first[:x].must_equal true
114
+ end
115
+ end
116
+
117
+ describe "PostgreSQL Immutable Trigger" do
118
+ before do
119
+ DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
120
+ DB.pgt_immutable(:accounts, :balance, :function_name=>:spgt_immutable)
121
+ DB[:accounts].insert(:id=>1)
122
+ end
123
+
124
+ after do
125
+ DB.drop_table(:accounts)
126
+ DB.drop_function(:spgt_immutable)
127
+ end
128
+
129
+ it "should allow modifying columns not marked as immutable" do
130
+ DB[:accounts].update(:id=>2)
131
+ end
132
+
133
+ it "should allow updating a column to its existing value" do
134
+ DB[:accounts].update(:balance=>0)
135
+ DB[:accounts].update(:balance=>Sequel.*(:balance, :balance))
136
+ end
137
+
138
+ it "should not allow modifying a column's value" do
139
+ proc{DB[:accounts].update(:balance=>1)}.must_raise(Sequel::DatabaseError)
140
+ end
141
+
142
+ it "should handle NULL values correctly" do
143
+ proc{DB[:accounts].update(:balance=>nil)}.must_raise(Sequel::DatabaseError)
144
+ DB[:accounts].delete
145
+ DB[:accounts].insert(:id=>1, :balance=>nil)
146
+ DB[:accounts].update(:balance=>nil)
147
+ proc{DB[:accounts].update(:balance=>0)}.must_raise(Sequel::DatabaseError)
148
+ end
149
+ end
150
+
151
+ describe "PostgreSQL Immutable Trigger" do
20
152
  before do
21
- DB.create_language(:plpgsql) if DB.server_version < 90000
153
+ DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
154
+ DB.pgt_immutable(:accounts, :balance)
155
+ DB[:accounts].insert(:id=>1)
22
156
  end
157
+
158
+ after do
159
+ DB.drop_table(:accounts)
160
+ DB.drop_function(:pgt_im_balance)
161
+ end
162
+
163
+ it "should work when called without options" do
164
+ DB[:accounts].update(:id=>2)
165
+ end
166
+ end
167
+
168
+ describe "PostgreSQL Sum Cache Trigger" do
169
+ before do
170
+ DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
171
+ DB.create_table(:entries){integer :id; integer :account_id; integer :amount}
172
+ DB.pgt_sum_cache(:accounts, :id, :balance, :entries, :account_id, :amount, :function_name=>:spgt_sum_cache)
173
+ DB[:accounts].insert(:id=>1)
174
+ DB[:accounts].insert(:id=>2)
175
+ end
176
+
177
+ after do
178
+ DB.drop_table(:entries, :accounts)
179
+ DB.drop_function(:spgt_sum_cache)
180
+ end
181
+
182
+ it "should not modify sum cache if adding/removing records fails due to ON CONFLICT DO NOTHING" do
183
+ DB.alter_table(:entries){add_unique_constraint :account_id}
184
+ DB[:entries].insert(:id=>1, :account_id=>1, :amount=>5)
185
+ DB[:accounts].order(:id).select_map(:balance).must_equal [5, 0]
186
+ DB[:entries].insert_conflict.insert(:id=>1, :account_id=>1, :amount=>10)
187
+ DB[:accounts].order(:id).select_map(:balance).must_equal [5, 0]
188
+ end
189
+
190
+ it "should modify sum cache when adding, updating, or removing records" do
191
+ DB[:accounts].order(:id).select_map(:balance).must_equal [0, 0]
192
+
193
+ DB[:entries].insert(:id=>1, :account_id=>1, :amount=>100)
194
+ DB[:accounts].order(:id).select_map(:balance).must_equal [100, 0]
195
+
196
+ DB[:entries].insert(:id=>2, :account_id=>1, :amount=>200)
197
+ DB[:accounts].order(:id).select_map(:balance).must_equal [300, 0]
198
+
199
+ DB[:entries].insert(:id=>3, :account_id=>nil, :amount=>500)
200
+ DB[:accounts].order(:id).select_map(:balance).must_equal [300, 0]
201
+
202
+ DB[:entries].where(:id=>3).update(:account_id=>2)
203
+ DB[:accounts].order(:id).select_map(:balance).must_equal [300, 500]
204
+
205
+ DB[:entries].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
206
+ DB[:accounts].order(:id).select_map(:balance).must_equal [400, 1000]
207
+
208
+ DB[:entries].where(:id=>2).update(:account_id=>2)
209
+ DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1200]
210
+
211
+ DB[:entries].where(:id=>2).update(:account_id=>nil)
212
+ DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
213
+
214
+ DB[:entries].where(:id=>2).update(:id=>4)
215
+ DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
216
+
217
+ DB[:entries].where(:id=>4).update(:account_id=>2)
218
+ DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1200]
219
+
220
+ DB[:entries].where(:id=>4).update(:account_id=>nil)
221
+ DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
222
+
223
+ DB[:entries].filter(:id=>4).delete
224
+ DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
225
+
226
+ DB[:entries].delete
227
+ DB[:accounts].order(:id).select_map(:balance).must_equal [0, 0]
228
+ end
229
+ end
230
+
231
+ describe "PostgreSQL Sum Cache Trigger with arbitrary expression" do
232
+ before do
233
+ DB.create_table(:accounts){integer :id; integer :nonzero_entries_count, :default=>0}
234
+ DB.create_table(:entries){integer :id; integer :account_id; integer :amount}
235
+ DB.pgt_sum_cache(:accounts, :id, :nonzero_entries_count, :entries, :account_id, Sequel.case({0=>0}, 1, :amount), :function_name=>:spgt_sum_cache)
236
+ DB[:accounts].insert(:id=>1)
237
+ DB[:accounts].insert(:id=>2)
238
+ end
239
+
23
240
  after do
24
- DB.drop_language(:plpgsql, :cascade=>true) if DB.server_version < 90000
25
- end
26
-
27
- describe "PostgreSQL Counter Cache Trigger" do
28
- before do
29
- DB.create_table(:accounts){integer :id; integer :num_entries, :default=>0}
30
- DB.create_table(:entries){integer :id; integer :account_id}
31
- DB.pgt_counter_cache(:accounts, :id, :num_entries, :entries, :account_id, :function_name=>:spgt_counter_cache)
32
- DB[:accounts].insert(:id=>1)
33
- DB[:accounts].insert(:id=>2)
34
- end
35
-
36
- after do
37
- DB.drop_table(:entries, :accounts)
38
- DB.drop_function(:spgt_counter_cache)
39
- end
40
-
41
- it "Should modify counter cache when adding or removing records" do
42
- DB[:accounts].order(:id).select_map(:num_entries).must_equal [0, 0]
43
-
44
- DB[:entries].insert(:id=>1, :account_id=>1)
45
- DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 0]
46
-
47
- DB[:entries].insert(:id=>2, :account_id=>1)
48
- DB[:accounts].order(:id).select_map(:num_entries).must_equal [2, 0]
49
-
50
- DB[:entries].insert(:id=>3, :account_id=>nil)
51
- DB[:accounts].order(:id).select_map(:num_entries).must_equal [2, 0]
52
-
53
- DB[:entries].where(:id=>3).update(:account_id=>2)
54
- DB[:accounts].order(:id).select_map(:num_entries).must_equal [2, 1]
55
-
56
- DB[:entries].where(:id=>2).update(:account_id=>2)
57
- DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 2]
58
-
59
- DB[:entries].where(:id=>2).update(:account_id=>nil)
60
- DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
61
-
62
- DB[:entries].where(:id=>2).update(:id=>4)
63
- DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
64
-
65
- DB[:entries].where(:id=>4).update(:account_id=>2)
66
- DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 2]
67
-
68
- DB[:entries].where(:id=>4).update(:account_id=>nil)
69
- DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
70
-
71
- DB[:entries].filter(:id=>4).delete
72
- DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
73
-
74
- DB[:entries].delete
75
- DB[:accounts].order(:id).select_map(:num_entries).must_equal [0, 0]
76
- end
77
- end
78
-
79
- describe "PostgreSQL Created At Trigger" do
80
- before do
81
- DB.create_table(:accounts){integer :id; timestamp :added_on}
82
- DB.pgt_created_at(:accounts, :added_on, :function_name=>:spgt_created_at)
83
- end
84
-
85
- after do
86
- DB.drop_table(:accounts)
87
- DB.drop_function(:spgt_created_at)
88
- end
89
-
90
- it "Should set the column upon insertion and ignore modifications afterward" do
91
- DB[:accounts].insert(:id=>1)
92
- t = DB[:accounts].get(:added_on)
93
- t.strftime('%F').must_equal Date.today.strftime('%F')
94
- DB[:accounts].update(:added_on=>Date.today - 60)
95
- DB[:accounts].get(:added_on).must_equal t
96
- DB[:accounts].insert(:id=>2)
97
- ds = DB[:accounts].select(:added_on)
98
- DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>1)).as(:x)).first[:x].must_equal true
99
- DB[:accounts].filter(:id=>1).update(:id=>3)
100
- DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>3)).as(:x)).first[:x].must_equal true
101
- end
102
- end
103
-
104
- describe "PostgreSQL Immutable Trigger" do
105
- before do
106
- DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
107
- DB.pgt_immutable(:accounts, :balance, :function_name=>:spgt_immutable)
108
- DB[:accounts].insert(:id=>1)
109
- end
110
-
111
- after do
112
- DB.drop_table(:accounts)
113
- DB.drop_function(:spgt_immutable)
114
- end
115
-
116
- it "Should allow modifying columns not marked as immutable" do
117
- DB[:accounts].update(:id=>2)
118
- end
119
-
120
- it "Should allow updating a column to its existing value" do
121
- DB[:accounts].update(:balance=>0)
122
- DB[:accounts].update(:balance=>Sequel.*(:balance, :balance))
123
- end
124
-
125
- it "Should not allow modifying a column's value" do
126
- proc{DB[:accounts].update(:balance=>1)}.must_raise(Sequel::DatabaseError)
127
- end
128
-
129
- it "Should handle NULL values correctly" do
130
- proc{DB[:accounts].update(:balance=>nil)}.must_raise(Sequel::DatabaseError)
131
- DB[:accounts].delete
132
- DB[:accounts].insert(:id=>1, :balance=>nil)
133
- DB[:accounts].update(:balance=>nil)
134
- proc{DB[:accounts].update(:balance=>0)}.must_raise(Sequel::DatabaseError)
135
- end
136
- end
137
-
138
- describe "PostgreSQL Sum Cache Trigger" do
139
- before do
140
- DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
141
- DB.create_table(:entries){integer :id; integer :account_id; integer :amount}
142
- DB.pgt_sum_cache(:accounts, :id, :balance, :entries, :account_id, :amount, :function_name=>:spgt_sum_cache)
143
- DB[:accounts].insert(:id=>1)
144
- DB[:accounts].insert(:id=>2)
145
- end
146
-
147
- after do
148
- DB.drop_table(:entries, :accounts)
149
- DB.drop_function(:spgt_sum_cache)
150
- end
151
-
152
- it "Should modify sum cache when adding, updating, or removing records" do
153
- DB[:accounts].order(:id).select_map(:balance).must_equal [0, 0]
154
-
155
- DB[:entries].insert(:id=>1, :account_id=>1, :amount=>100)
156
- DB[:accounts].order(:id).select_map(:balance).must_equal [100, 0]
157
-
158
- DB[:entries].insert(:id=>2, :account_id=>1, :amount=>200)
159
- DB[:accounts].order(:id).select_map(:balance).must_equal [300, 0]
160
-
161
- DB[:entries].insert(:id=>3, :account_id=>nil, :amount=>500)
162
- DB[:accounts].order(:id).select_map(:balance).must_equal [300, 0]
163
-
164
- DB[:entries].where(:id=>3).update(:account_id=>2)
165
- DB[:accounts].order(:id).select_map(:balance).must_equal [300, 500]
166
-
167
- DB[:entries].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
168
- DB[:accounts].order(:id).select_map(:balance).must_equal [400, 1000]
169
-
170
- DB[:entries].where(:id=>2).update(:account_id=>2)
171
- DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1200]
172
-
173
- DB[:entries].where(:id=>2).update(:account_id=>nil)
174
- DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
175
-
176
- DB[:entries].where(:id=>2).update(:id=>4)
177
- DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
178
-
179
- DB[:entries].where(:id=>4).update(:account_id=>2)
180
- DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1200]
181
-
182
- DB[:entries].where(:id=>4).update(:account_id=>nil)
183
- DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
184
-
185
- DB[:entries].filter(:id=>4).delete
186
- DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
187
-
188
- DB[:entries].delete
189
- DB[:accounts].order(:id).select_map(:balance).must_equal [0, 0]
190
- end
191
- end
192
-
193
- describe "PostgreSQL Sum Cache Trigger with arbitrary expression" do
194
- before do
195
- DB.create_table(:accounts){integer :id; integer :nonzero_entries_count, :default=>0}
196
- DB.create_table(:entries){integer :id; integer :account_id; integer :amount}
197
- DB.pgt_sum_cache(:accounts, :id, :nonzero_entries_count, :entries, :account_id, Sequel.case({0=>0}, 1, :amount), :function_name=>:spgt_sum_cache)
198
- DB[:accounts].insert(:id=>1)
199
- DB[:accounts].insert(:id=>2)
200
- end
201
-
202
- after do
203
- DB.drop_table(:entries, :accounts)
204
- DB.drop_function(:spgt_sum_cache)
205
- end
206
-
207
- it "Should modify sum cache when adding, updating, or removing records" do
208
- DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
209
-
210
- DB[:entries].insert(:id=>1, :account_id=>1, :amount=>100)
211
- DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 0]
212
-
213
- DB[:entries].insert(:id=>2, :account_id=>1, :amount=>200)
214
- DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
215
-
216
- DB[:entries].insert(:id=>3, :account_id=>nil, :amount=>500)
217
- DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
218
-
219
- DB[:entries].where(:id=>3).update(:account_id=>2)
220
- DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
221
-
222
- DB[:entries].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
223
- DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
224
-
225
- DB[:entries].where(:id=>2).update(:account_id=>2)
226
- DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
227
-
228
- DB[:entries].where(:id=>2).update(:account_id=>nil)
229
- DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
230
-
231
- DB[:entries].where(:id=>2).update(:id=>4)
232
- DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
233
-
234
- DB[:entries].where(:id=>4).update(:account_id=>2)
235
- DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
236
-
237
- DB[:entries].where(:id=>4).update(:account_id=>nil)
238
- DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
239
-
240
- DB[:entries].filter(:id=>4).delete
241
- DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
242
-
243
- DB[:entries].delete
244
- DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
245
- end
246
- end
247
-
248
-
249
- describe "PostgreSQL Sum Through Many Cache Trigger" do
250
- before do
251
- DB.create_table(:parents){primary_key :id; integer :balance, :default=>0, :null=>false}
252
- DB.create_table(:children){primary_key :id; integer :amount, :null=>false}
253
- DB.create_table(:links){integer :parent_id, :null=>false; integer :child_id, :null=>false; unique [:parent_id, :child_id]}
254
- DB.pgt_sum_through_many_cache(
255
- :main_table=>:parents,
256
- :sum_column=>:balance,
257
- :summed_table=>:children,
258
- :summed_column=>:amount,
259
- :join_table=>:links,
260
- :main_table_fk_column=>:parent_id,
261
- :summed_table_fk_column=>:child_id,
262
- :function_name=>:spgt_stm_cache,
263
- :join_function_name=>:spgt_stm_cache_join
264
- )
265
- DB[:parents].insert(:id=>1)
266
- DB[:parents].insert(:id=>2)
267
- end
268
-
269
- after do
270
- DB.drop_table(:links, :parents, :children)
271
- DB.drop_function(:spgt_stm_cache)
272
- DB.drop_function(:spgt_stm_cache_join)
273
- end
274
-
275
- it "Should modify sum cache when adding, updating, or removing records" do
276
- DB[:parents].order(:id).select_map(:balance).must_equal [0, 0]
277
-
278
- DB[:children].insert(:id=>1, :amount=>100)
279
- DB[:links].insert(:parent_id=>1, :child_id=>1)
280
- DB[:parents].order(:id).select_map(:balance).must_equal [100, 0]
281
-
282
- DB[:children].insert(:id=>2, :amount=>200)
283
- DB[:links].insert(:parent_id=>1, :child_id=>2)
284
- DB[:parents].order(:id).select_map(:balance).must_equal [300, 0]
285
-
286
- DB[:children].insert(:id=>3, :amount=>500)
287
- DB[:parents].order(:id).select_map(:balance).must_equal [300, 0]
288
- DB[:links].insert(:parent_id=>2, :child_id=>3)
289
- DB[:parents].order(:id).select_map(:balance).must_equal [300, 500]
241
+ DB.drop_table(:entries, :accounts)
242
+ DB.drop_function(:spgt_sum_cache)
243
+ end
244
+
245
+ it "should modify sum cache when adding, updating, or removing records" do
246
+ DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
290
247
 
291
- DB[:links].where(:parent_id=>2, :child_id=>3).update(:parent_id=>1)
292
- DB[:parents].order(:id).select_map(:balance).must_equal [800, 0]
293
-
294
- DB[:children].insert(:id=>4, :amount=>400)
295
- DB[:links].where(:parent_id=>1, :child_id=>3).update(:child_id=>4)
296
- DB[:parents].order(:id).select_map(:balance).must_equal [700, 0]
248
+ DB[:entries].insert(:id=>1, :account_id=>1, :amount=>100)
249
+ DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 0]
297
250
 
298
- DB[:links].where(:parent_id=>1, :child_id=>4).update(:parent_id=>2, :child_id=>3)
299
- DB[:parents].order(:id).select_map(:balance).must_equal [300, 500]
251
+ DB[:entries].insert(:id=>2, :account_id=>1, :amount=>200)
252
+ DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
300
253
 
301
- DB[:children].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
302
- DB[:parents].order(:id).select_map(:balance).must_equal [400, 1000]
254
+ DB[:entries].insert(:id=>3, :account_id=>nil, :amount=>500)
255
+ DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
303
256
 
304
- DB[:links].where(:parent_id=>1, :child_id=>2).update(:parent_id=>2)
305
- DB[:parents].order(:id).select_map(:balance).must_equal [200, 1200]
257
+ DB[:entries].where(:id=>3).update(:account_id=>2)
258
+ DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
306
259
 
307
- DB[:links].where(:parent_id=>2, :child_id=>2).update(:parent_id=>1)
308
- DB[:parents].order(:id).select_map(:balance).must_equal [400, 1000]
260
+ DB[:entries].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
261
+ DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
309
262
 
310
- DB[:links].where(:parent_id=>1, :child_id=>2).update(:child_id=>3)
311
- DB[:parents].order(:id).select_map(:balance).must_equal [1200, 1000]
263
+ DB[:entries].where(:id=>2).update(:account_id=>2)
264
+ DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
312
265
 
313
- DB[:links].insert(:parent_id=>2, :child_id=>4)
314
- DB[:parents].order(:id).select_map(:balance).must_equal [1200, 1800]
266
+ DB[:entries].where(:id=>2).update(:account_id=>nil)
267
+ DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
315
268
 
316
- DB[:children].filter(:id=>4).delete
317
- DB[:parents].order(:id).select_map(:balance).must_equal [1200, 1000]
269
+ DB[:entries].where(:id=>2).update(:id=>4)
270
+ DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
318
271
 
319
- DB[:links].filter(:parent_id=>1, :child_id=>1).delete
320
- DB[:parents].order(:id).select_map(:balance).must_equal [1000, 1000]
272
+ DB[:entries].where(:id=>4).update(:account_id=>2)
273
+ DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
321
274
 
322
- DB[:children].insert(:id=>4, :amount=>400)
323
- DB[:parents].order(:id).select_map(:balance).must_equal [1000, 1400]
275
+ DB[:entries].where(:id=>4).update(:account_id=>nil)
276
+ DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
324
277
 
325
- DB[:children].delete
326
- DB[:parents].order(:id).select_map(:balance).must_equal [0, 0]
278
+ DB[:entries].filter(:id=>4).delete
279
+ DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
280
+
281
+ DB[:entries].delete
282
+ DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
283
+ end
284
+ end
327
285
 
328
- DB[:children].multi_insert([{:id=>2, :amount=>200}, {:id=>1, :amount=>200}, {:id=>3, :amount=>1000}, {:id=>4, :amount=>400}])
329
- DB[:parents].order(:id).select_map(:balance).must_equal [1000, 1400]
330
286
 
331
- DB[:links].where(:child_id=>3).update(:child_id=>2)
332
- DB[:parents].order(:id).select_map(:balance).must_equal [200, 600]
287
+ describe "PostgreSQL Sum Through Many Cache Trigger" do
288
+ before do
289
+ DB.create_table(:parents){primary_key :id; integer :balance, :default=>0, :null=>false}
290
+ DB.create_table(:children){primary_key :id; integer :amount, :null=>false}
291
+ DB.create_table(:links){integer :parent_id, :null=>false; integer :child_id, :null=>false; unique [:parent_id, :child_id]}
292
+ DB.pgt_sum_through_many_cache(
293
+ :main_table=>:parents,
294
+ :sum_column=>:balance,
295
+ :summed_table=>:children,
296
+ :summed_column=>:amount,
297
+ :join_table=>:links,
298
+ :main_table_fk_column=>:parent_id,
299
+ :summed_table_fk_column=>:child_id,
300
+ :function_name=>:spgt_stm_cache,
301
+ :join_function_name=>:spgt_stm_cache_join
302
+ )
303
+ DB[:parents].insert(:id=>1)
304
+ DB[:parents].insert(:id=>2)
305
+ end
333
306
 
334
- DB[:children].update(:amount=>10)
335
- DB[:parents].order(:id).select_map(:balance).must_equal [10, 20]
307
+ after do
308
+ DB.drop_table(:links, :parents, :children)
309
+ DB.drop_function(:spgt_stm_cache)
310
+ DB.drop_function(:spgt_stm_cache_join)
311
+ end
336
312
 
337
- DB[:links].delete
338
- DB[:parents].order(:id).select_map(:balance).must_equal [0, 0]
339
- end
313
+ it "should not modify sum cache if adding/removing records fails due to ON CONFLICT DO NOTHING" do
314
+ DB.alter_table(:children){add_unique_constraint :amount}
315
+ DB[:children].insert(:id=>1, :amount=>5)
316
+ DB[:links].insert(:parent_id=>1, :child_id=>1)
317
+ DB[:parents].order(:id).select_map(:balance).must_equal [5, 0]
318
+ DB[:children].insert_conflict.insert(:id=>1, :amount=>5)
319
+ DB[:parents].order(:id).select_map(:balance).must_equal [5, 0]
320
+ DB[:links].insert_conflict.insert(:parent_id=>1, :child_id=>1)
321
+ DB[:parents].order(:id).select_map(:balance).must_equal [5, 0]
340
322
  end
341
323
 
342
- describe "PostgreSQL Sum Through Many Cache Trigger with arbitrary expression" do
343
- before do
344
- DB.create_table(:parents){primary_key :id; integer :nonzero_entries_count, :default=>0, :null=>false}
345
- DB.create_table(:children){primary_key :id; integer :amount, :null=>false}
346
- DB.create_table(:links){integer :parent_id, :null=>false; integer :child_id, :null=>false; unique [:parent_id, :child_id]}
347
- DB.pgt_sum_through_many_cache(
348
- :main_table=>:parents,
349
- :sum_column=>:nonzero_entries_count,
350
- :summed_table=>:children,
351
- :summed_column=>Sequel.case({0=>0}, 1, :amount),
352
- :join_table=>:links,
353
- :main_table_fk_column=>:parent_id,
354
- :summed_table_fk_column=>:child_id,
355
- :function_name=>:spgt_stm_cache,
356
- :join_function_name=>:spgt_stm_cache_join
357
- )
358
- DB[:parents].insert(:id=>1)
359
- DB[:parents].insert(:id=>2)
360
- end
324
+ it "should modify sum cache when adding, updating, or removing records" do
325
+ DB[:parents].order(:id).select_map(:balance).must_equal [0, 0]
361
326
 
362
- after do
363
- DB.drop_table(:links, :parents, :children)
364
- DB.drop_function(:spgt_stm_cache)
365
- DB.drop_function(:spgt_stm_cache_join)
366
- end
327
+ DB[:children].insert(:id=>1, :amount=>100)
328
+ DB[:links].insert(:parent_id=>1, :child_id=>1)
329
+ DB[:parents].order(:id).select_map(:balance).must_equal [100, 0]
367
330
 
368
- it "Should modify sum cache when adding, updating, or removing records" do
369
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
331
+ DB[:children].insert(:id=>2, :amount=>200)
332
+ DB[:links].insert(:parent_id=>1, :child_id=>2)
333
+ DB[:parents].order(:id).select_map(:balance).must_equal [300, 0]
370
334
 
371
- DB[:children].insert(:id=>1, :amount=>100)
372
- DB[:links].insert(:parent_id=>1, :child_id=>1)
373
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 0]
335
+ DB[:children].insert(:id=>3, :amount=>500)
336
+ DB[:parents].order(:id).select_map(:balance).must_equal [300, 0]
337
+ DB[:links].insert(:parent_id=>2, :child_id=>3)
338
+ DB[:parents].order(:id).select_map(:balance).must_equal [300, 500]
374
339
 
375
- DB[:children].insert(:id=>2, :amount=>200)
376
- DB[:links].insert(:parent_id=>1, :child_id=>2)
377
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
340
+ DB[:links].where(:parent_id=>2, :child_id=>3).update(:parent_id=>1)
341
+ DB[:parents].order(:id).select_map(:balance).must_equal [800, 0]
378
342
 
379
- DB[:children].insert(:id=>3, :amount=>500)
380
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
381
- DB[:links].insert(:parent_id=>2, :child_id=>3)
382
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
343
+ DB[:children].insert(:id=>4, :amount=>400)
344
+ DB[:links].where(:parent_id=>1, :child_id=>3).update(:child_id=>4)
345
+ DB[:parents].order(:id).select_map(:balance).must_equal [700, 0]
383
346
 
384
- DB[:links].where(:parent_id=>2, :child_id=>3).update(:parent_id=>1)
385
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [3, 0]
347
+ DB[:links].where(:parent_id=>1, :child_id=>4).update(:parent_id=>2, :child_id=>3)
348
+ DB[:parents].order(:id).select_map(:balance).must_equal [300, 500]
386
349
 
387
- DB[:children].insert(:id=>4, :amount=>400)
388
- DB[:links].where(:parent_id=>1, :child_id=>3).update(:child_id=>4)
389
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [3, 0]
350
+ DB[:children].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
351
+ DB[:parents].order(:id).select_map(:balance).must_equal [400, 1000]
390
352
 
391
- DB[:links].where(:parent_id=>1, :child_id=>4).update(:parent_id=>2, :child_id=>3)
392
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
353
+ DB[:links].where(:parent_id=>1, :child_id=>2).update(:parent_id=>2)
354
+ DB[:parents].order(:id).select_map(:balance).must_equal [200, 1200]
393
355
 
394
- DB[:children].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
395
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
356
+ DB[:links].where(:parent_id=>2, :child_id=>2).update(:parent_id=>1)
357
+ DB[:parents].order(:id).select_map(:balance).must_equal [400, 1000]
396
358
 
397
- DB[:links].where(:parent_id=>1, :child_id=>2).update(:parent_id=>2)
398
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
359
+ DB[:links].where(:parent_id=>1, :child_id=>2).update(:child_id=>3)
360
+ DB[:parents].order(:id).select_map(:balance).must_equal [1200, 1000]
399
361
 
400
- DB[:links].where(:parent_id=>2, :child_id=>2).update(:parent_id=>1)
401
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
362
+ DB[:links].insert(:parent_id=>2, :child_id=>4)
363
+ DB[:parents].order(:id).select_map(:balance).must_equal [1200, 1800]
402
364
 
403
- DB[:links].where(:parent_id=>1, :child_id=>2).update(:child_id=>3)
404
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
405
-
406
- DB[:links].insert(:parent_id=>2, :child_id=>4)
407
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 2]
408
-
409
- DB[:children].filter(:id=>4).delete
410
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
411
-
412
- DB[:links].filter(:parent_id=>1, :child_id=>1).delete
413
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
414
-
415
- DB[:children].insert(:id=>4, :amount=>400)
416
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
417
-
418
- DB[:children].delete
419
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
420
-
421
- DB[:children].multi_insert([{:id=>2, :amount=>200}, {:id=>1, :amount=>200}, {:id=>3, :amount=>1000}, {:id=>4, :amount=>400}])
422
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
423
-
424
- DB[:links].where(:child_id=>3).update(:child_id=>2)
425
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
426
-
427
- DB[:children].update(:amount=>10)
428
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
429
-
430
- DB[:links].delete
431
- DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
432
- end
433
- end
434
-
435
- describe "PostgreSQL Updated At Trigger" do
436
- before do
437
- DB.create_table(:accounts){integer :id; timestamp :changed_on}
438
- DB.pgt_updated_at(:accounts, :changed_on, :function_name=>:spgt_updated_at)
439
- end
440
-
441
- after do
442
- DB.drop_table(:accounts)
443
- DB.drop_function(:spgt_updated_at)
444
- end
445
-
446
- it "Should set the column always to the current timestamp" do
447
- DB[:accounts].insert(:id=>1)
448
- t = DB[:accounts].get(:changed_on)
449
- t.strftime('%F').must_equal Date.today.strftime('%F')
450
- DB[:accounts].insert(:id=>2)
451
- ds = DB[:accounts].select(:changed_on)
452
- DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>1)).as(:x)).first[:x].must_equal true
453
- DB[:accounts].filter(:id=>1).update(:id=>3)
454
- DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>3)) > ds.filter(:id=>2)).as(:x)).first[:x].must_equal true
455
- end
456
- end
457
-
458
- describe "PostgreSQL Touch Trigger" do
459
- before do
460
- DB.create_table(:parents){integer :id1; integer :id2; integer :child_id; timestamp :changed_on}
461
- DB.create_table(:children){integer :id; integer :parent_id1; integer :parent_id2; timestamp :changed_on}
462
- end
463
-
464
- after do
465
- DB.drop_table(:children, :parents)
466
- DB.drop_function(:spgt_touch)
467
- DB.drop_function(:spgt_touch2) if @spgt_touch2
468
- end
469
-
470
- it "Should update the timestamp column of the related table when adding, updating or removing records" do
471
- DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
472
- d = Date.today
473
- d30 = Date.today - 30
474
- DB[:parents].insert(:id1=>1, :changed_on=>d30)
475
- DB[:parents].insert(:id1=>2, :changed_on=>d30)
476
- DB[:children].insert(:id=>1, :parent_id1=>1)
477
- DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d.strftime('%F'), d30.strftime('%F')]
478
-
479
- DB[:parents].update(:changed_on=>d30)
480
- DB[:children].update(:id=>2)
481
- DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d.strftime('%F'), d30.strftime('%F')]
482
-
483
- DB[:parents].update(:changed_on=>d30)
484
- DB[:children].update(:parent_id1=>2)
485
- DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d.strftime('%F'), d.strftime('%F')]
486
-
487
- DB[:parents].update(:changed_on=>d30)
488
- DB[:children].update(:parent_id1=>nil)
489
- DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d.strftime('%F')]
490
-
491
- DB[:parents].update(:changed_on=>d30)
492
- DB[:children].update(:parent_id2=>1)
493
- DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d30.strftime('%F')]
494
-
495
- DB[:parents].update(:changed_on=>d30)
496
- DB[:children].update(:parent_id1=>2)
497
- DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d.strftime('%F')]
498
-
499
- DB[:parents].update(:changed_on=>d30)
500
- DB[:children].delete
501
- DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d.strftime('%F')]
502
-
503
- DB[:parents].update(:changed_on=>d30)
504
- DB[:children].insert(:id=>2, :parent_id1=>nil)
505
- DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d30.strftime('%F')]
506
- DB[:children].where(:id=>2).delete
507
- DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d30.strftime('%F')]
508
- end
509
-
510
- it "Should update the timestamp column of the related table when there is a composite foreign key" do
511
- DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1, :id2=>:parent_id2}, :function_name=>:spgt_touch)
512
- DB[:parents].insert(:id1=>1, :id2=>2, :changed_on=>Date.today - 30)
513
- DB[:children].insert(:id=>1, :parent_id1=>1, :parent_id2=>2)
514
- DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
515
- DB[:parents].update(:changed_on=>Date.today - 30)
516
- DB[:children].update(:id=>2)
517
- DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
518
- DB[:parents].update(:changed_on=>Date.today - 30)
519
- DB[:children].delete
520
- DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
521
- end
522
-
523
- it "Should update timestamps correctly when two tables touch each other" do
524
- DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
525
- @spgt_touch2 = true
526
- DB.pgt_touch(:parents, :children, :changed_on, {:id=>:child_id}, :function_name=>:spgt_touch2)
527
- DB[:parents].insert(:id1=>1, :child_id=>1, :changed_on=>Date.today - 30)
528
- DB[:children].insert(:id=>1, :parent_id1=>1, :changed_on=>Date.today - 30)
529
- DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
530
- DB[:children].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
531
- time = DB[:parents].get(:changed_on)
532
- DB[:parents].update(:id2=>4)
533
- DB[:parents].get(:changed_on).must_be :>, time
534
- DB[:children].get(:changed_on).must_be :>, time
535
- time = DB[:parents].get(:changed_on)
536
- DB[:children].update(:id=>1)
537
- DB[:parents].get(:changed_on).must_be :>, time
538
- DB[:children].get(:changed_on).must_be :>, time
539
- time = DB[:parents].get(:changed_on)
540
- DB[:children].delete
541
- DB[:parents].get(:changed_on).must_be :>, time
542
- end
543
-
544
- it "Should update the timestamp on the related table if that timestamp is initially NULL" do
545
- DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
546
- DB[:parents].insert(:id1=>1, :changed_on=>nil)
547
- DB[:children].insert(:id=>1, :parent_id1=>1)
548
- changed_on = DB[:parents].get(:changed_on)
549
- changed_on.wont_equal nil
550
- changed_on.strftime('%F').must_equal Date.today.strftime('%F')
551
- end
552
- end
553
-
554
- describe "PostgreSQL Array Foreign Key Trigger" do
555
- before do
556
- DB.create_table(:accounts){Integer :id, :primary_key=>true}
557
- DB.create_table(:entries){Integer :id, :primary_key=>true; column :account_ids, 'integer[]'}
558
- DB.pgt_foreign_key_array(:table=>:entries, :column=>:account_ids, :referenced_table=>:accounts, :referenced_column=>:id, :function_name=>:spgt_foreign_key_array, :referenced_function_name=>:spgt_referenced_foreign_key_array)
559
- end
560
-
561
- after do
562
- DB.drop_table(:entries, :accounts)
563
- DB.drop_function(:spgt_foreign_key_array)
564
- DB.drop_function(:spgt_referenced_foreign_key_array)
565
- end
566
-
567
- it "should raise error for queries that violate referential integrity, and allow other queries" do
568
- proc{DB[:entries].insert(:id=>10, :account_ids=>Sequel.pg_array([1]))}.must_raise Sequel::DatabaseError
569
- DB[:entries].insert(:id=>10, :account_ids=>nil)
570
- DB[:entries].update(:account_ids=>Sequel.pg_array([], :integer))
571
- DB[:accounts].insert(:id=>1)
572
- proc{DB[:entries].insert(:id=>10, :account_ids=>Sequel.pg_array([1, 1]))}.must_raise Sequel::DatabaseError
573
- DB[:entries].update(:account_ids=>Sequel.pg_array([1]))
574
- proc{DB[:entries].update(:account_ids=>Sequel.pg_array([2]))}.must_raise Sequel::DatabaseError
575
- DB[:accounts].insert(:id=>2)
576
- proc{DB[:entries].insert(:id=>10, :account_ids=>Sequel.pg_array([[1], [2]]))}.must_raise Sequel::DatabaseError
577
- DB[:entries].update(:account_ids=>Sequel.pg_array([2]))
578
- DB[:entries].all.must_equal [{:id=>10, :account_ids=>[2]}]
579
- DB[:entries].update(:account_ids=>Sequel.pg_array([1, 2]))
580
- DB[:entries].all.must_equal [{:id=>10, :account_ids=>[1, 2]}]
581
- DB[:entries].update(:account_ids=>Sequel.pg_array([1]))
582
- DB[:accounts].where(:id=>1).update(:id=>1)
583
- DB[:accounts].where(:id=>2).update(:id=>3)
584
- proc{DB[:accounts].where(:id=>1).update(:id=>2)}.must_raise Sequel::DatabaseError
585
- proc{DB[:accounts].where(:id=>1).delete}.must_raise Sequel::DatabaseError
586
- DB[:accounts].where(:id=>3).count.must_equal 1
587
- DB[:accounts].where(:id=>3).delete
588
- proc{DB[:accounts].delete}.must_raise Sequel::DatabaseError
589
- DB[:entries].delete
590
- DB[:accounts].delete
591
- end
365
+ DB[:children].filter(:id=>4).delete
366
+ DB[:parents].order(:id).select_map(:balance).must_equal [1200, 1000]
367
+
368
+ DB[:links].filter(:parent_id=>1, :child_id=>1).delete
369
+ DB[:parents].order(:id).select_map(:balance).must_equal [1000, 1000]
370
+
371
+ DB[:children].insert(:id=>4, :amount=>400)
372
+ DB[:parents].order(:id).select_map(:balance).must_equal [1000, 1400]
373
+
374
+ DB[:children].delete
375
+ DB[:parents].order(:id).select_map(:balance).must_equal [0, 0]
376
+
377
+ DB[:children].multi_insert([{:id=>2, :amount=>200}, {:id=>1, :amount=>200}, {:id=>3, :amount=>1000}, {:id=>4, :amount=>400}])
378
+ DB[:parents].order(:id).select_map(:balance).must_equal [1000, 1400]
379
+
380
+ DB[:links].where(:child_id=>3).update(:child_id=>2)
381
+ DB[:parents].order(:id).select_map(:balance).must_equal [200, 600]
382
+
383
+ DB[:children].update(:amount=>10)
384
+ DB[:parents].order(:id).select_map(:balance).must_equal [10, 20]
385
+
386
+ DB[:links].delete
387
+ DB[:parents].order(:id).select_map(:balance).must_equal [0, 0]
592
388
  end
593
389
  end
390
+
391
+ describe "PostgreSQL Sum Through Many Cache Trigger with arbitrary expression" do
392
+ before do
393
+ DB.create_table(:parents){primary_key :id; integer :nonzero_entries_count, :default=>0, :null=>false}
394
+ DB.create_table(:children){primary_key :id; integer :amount, :null=>false}
395
+ DB.create_table(:links){integer :parent_id, :null=>false; integer :child_id, :null=>false; unique [:parent_id, :child_id]}
396
+ DB.pgt_sum_through_many_cache(
397
+ :main_table=>:parents,
398
+ :sum_column=>:nonzero_entries_count,
399
+ :summed_table=>:children,
400
+ :summed_column=>Sequel.case({0=>0}, 1, :amount),
401
+ :join_table=>:links,
402
+ :main_table_fk_column=>:parent_id,
403
+ :summed_table_fk_column=>:child_id,
404
+ :function_name=>:spgt_stm_cache,
405
+ :join_function_name=>:spgt_stm_cache_join
406
+ )
407
+ DB[:parents].insert(:id=>1)
408
+ DB[:parents].insert(:id=>2)
409
+ end
410
+
411
+ after do
412
+ DB.drop_table(:links, :parents, :children)
413
+ DB.drop_function(:spgt_stm_cache)
414
+ DB.drop_function(:spgt_stm_cache_join)
415
+ end
416
+
417
+ it "should modify sum cache when adding, updating, or removing records" do
418
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
419
+
420
+ DB[:children].insert(:id=>1, :amount=>100)
421
+ DB[:links].insert(:parent_id=>1, :child_id=>1)
422
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 0]
423
+
424
+ DB[:children].insert(:id=>2, :amount=>200)
425
+ DB[:links].insert(:parent_id=>1, :child_id=>2)
426
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
427
+
428
+ DB[:children].insert(:id=>3, :amount=>500)
429
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
430
+ DB[:links].insert(:parent_id=>2, :child_id=>3)
431
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
432
+
433
+ DB[:links].where(:parent_id=>2, :child_id=>3).update(:parent_id=>1)
434
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [3, 0]
435
+
436
+ DB[:children].insert(:id=>4, :amount=>400)
437
+ DB[:links].where(:parent_id=>1, :child_id=>3).update(:child_id=>4)
438
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [3, 0]
439
+
440
+ DB[:links].where(:parent_id=>1, :child_id=>4).update(:parent_id=>2, :child_id=>3)
441
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
442
+
443
+ DB[:children].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
444
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
445
+
446
+ DB[:links].where(:parent_id=>1, :child_id=>2).update(:parent_id=>2)
447
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
448
+
449
+ DB[:links].where(:parent_id=>2, :child_id=>2).update(:parent_id=>1)
450
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
451
+
452
+ DB[:links].where(:parent_id=>1, :child_id=>2).update(:child_id=>3)
453
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
454
+
455
+ DB[:links].insert(:parent_id=>2, :child_id=>4)
456
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 2]
457
+
458
+ DB[:children].filter(:id=>4).delete
459
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
460
+
461
+ DB[:links].filter(:parent_id=>1, :child_id=>1).delete
462
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
463
+
464
+ DB[:children].insert(:id=>4, :amount=>400)
465
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
466
+
467
+ DB[:children].delete
468
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
469
+
470
+ DB[:children].multi_insert([{:id=>2, :amount=>200}, {:id=>1, :amount=>200}, {:id=>3, :amount=>1000}, {:id=>4, :amount=>400}])
471
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
472
+
473
+ DB[:links].where(:child_id=>3).update(:child_id=>2)
474
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
475
+
476
+ DB[:children].update(:amount=>10)
477
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
478
+
479
+ DB[:links].delete
480
+ DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
481
+ end
482
+ end
483
+
484
+ describe "PostgreSQL Updated At Trigger" do
485
+ before do
486
+ DB.create_table(:accounts){integer :id; timestamp :changed_on}
487
+ DB.pgt_updated_at(:accounts, :changed_on, :function_name=>:spgt_updated_at)
488
+ end
489
+
490
+ after do
491
+ DB.drop_table(:accounts)
492
+ DB.drop_function(:spgt_updated_at)
493
+ end
494
+
495
+ it "should set the column always to the current timestamp" do
496
+ DB[:accounts].insert(:id=>1)
497
+ t = DB[:accounts].get(:changed_on)
498
+ t.strftime('%F').must_equal Date.today.strftime('%F')
499
+ DB[:accounts].insert(:id=>2)
500
+ ds = DB[:accounts].select(:changed_on)
501
+ DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>1)).as(:x)).first[:x].must_equal true
502
+ DB[:accounts].filter(:id=>1).update(:id=>3)
503
+ DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>3)) > ds.filter(:id=>2)).as(:x)).first[:x].must_equal true
504
+ end
505
+ end
506
+
507
+ describe "PostgreSQL Touch Trigger" do
508
+ before do
509
+ DB.create_table(:parents){integer :id1; integer :id2; integer :child_id; timestamp :changed_on}
510
+ DB.create_table(:children){integer :id; integer :parent_id1; integer :parent_id2; timestamp :changed_on}
511
+ end
512
+
513
+ after do
514
+ DB.drop_table(:children, :parents)
515
+ DB.drop_function(:spgt_touch)
516
+ DB.drop_function(:spgt_touch2) if @spgt_touch2
517
+ end
518
+
519
+ it "should not modify timestamp column of related table if adding/removing records fails due to ON CONFLICT DO NOTHING" do
520
+ DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
521
+ DB.alter_table(:children){add_unique_constraint :parent_id1}
522
+ d30 = Date.today - 30
523
+ DB[:children].insert(:id=>1, :parent_id1=>1)
524
+ DB[:parents].insert(:id1=>1, :changed_on=>d30)
525
+ DB[:parents].get(:changed_on).to_date.must_equal d30
526
+ DB[:children].insert_conflict.insert(:id=>1, :parent_id1=>1)
527
+ DB[:parents].get(:changed_on).to_date.must_equal d30
528
+ end
529
+
530
+ it "should update the timestamp column of the related table when adding, updating or removing records" do
531
+ DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
532
+ d = Date.today
533
+ d30 = Date.today - 30
534
+ DB[:parents].insert(:id1=>1, :changed_on=>d30)
535
+ DB[:parents].insert(:id1=>2, :changed_on=>d30)
536
+ DB[:children].insert(:id=>1, :parent_id1=>1)
537
+ DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d.strftime('%F'), d30.strftime('%F')]
538
+
539
+ DB[:parents].update(:changed_on=>d30)
540
+ DB[:children].update(:id=>2)
541
+ DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d.strftime('%F'), d30.strftime('%F')]
542
+
543
+ DB[:parents].update(:changed_on=>d30)
544
+ DB[:children].update(:parent_id1=>2)
545
+ DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d.strftime('%F'), d.strftime('%F')]
546
+
547
+ DB[:parents].update(:changed_on=>d30)
548
+ DB[:children].update(:parent_id1=>nil)
549
+ DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d.strftime('%F')]
550
+
551
+ DB[:parents].update(:changed_on=>d30)
552
+ DB[:children].update(:parent_id2=>1)
553
+ DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d30.strftime('%F')]
554
+
555
+ DB[:parents].update(:changed_on=>d30)
556
+ DB[:children].update(:parent_id1=>2)
557
+ DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d.strftime('%F')]
558
+
559
+ DB[:parents].update(:changed_on=>d30)
560
+ DB[:children].delete
561
+ DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d.strftime('%F')]
562
+
563
+ DB[:parents].update(:changed_on=>d30)
564
+ DB[:children].insert(:id=>2, :parent_id1=>nil)
565
+ DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d30.strftime('%F')]
566
+ DB[:children].where(:id=>2).delete
567
+ DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d30.strftime('%F')]
568
+ end
569
+
570
+ it "should update the timestamp column of the related table when there is a composite foreign key" do
571
+ DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1, :id2=>:parent_id2}, :function_name=>:spgt_touch)
572
+ DB[:parents].insert(:id1=>1, :id2=>2, :changed_on=>Date.today - 30)
573
+ DB[:children].insert(:id=>1, :parent_id1=>1, :parent_id2=>2)
574
+ DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
575
+ DB[:parents].update(:changed_on=>Date.today - 30)
576
+ DB[:children].update(:id=>2)
577
+ DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
578
+ DB[:parents].update(:changed_on=>Date.today - 30)
579
+ DB[:children].delete
580
+ DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
581
+ end
582
+
583
+ it "should update timestamps correctly when two tables touch each other" do
584
+ DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
585
+ @spgt_touch2 = true
586
+ DB.pgt_touch(:parents, :children, :changed_on, {:id=>:child_id}, :function_name=>:spgt_touch2)
587
+ DB[:parents].insert(:id1=>1, :child_id=>1, :changed_on=>Date.today - 30)
588
+ DB[:children].insert(:id=>1, :parent_id1=>1, :changed_on=>Date.today - 30)
589
+ DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
590
+ DB[:children].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
591
+ time = DB[:parents].get(:changed_on)
592
+ DB[:parents].update(:id2=>4)
593
+ DB[:parents].get(:changed_on).must_be :>, time
594
+ DB[:children].get(:changed_on).must_be :>, time
595
+ time = DB[:parents].get(:changed_on)
596
+ DB[:children].update(:id=>1)
597
+ DB[:parents].get(:changed_on).must_be :>, time
598
+ DB[:children].get(:changed_on).must_be :>, time
599
+ time = DB[:parents].get(:changed_on)
600
+ DB[:children].delete
601
+ DB[:parents].get(:changed_on).must_be :>, time
602
+ end
603
+
604
+ it "should update the timestamp on the related table if that timestamp is initially NULL" do
605
+ DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
606
+ DB[:parents].insert(:id1=>1, :changed_on=>nil)
607
+ DB[:children].insert(:id=>1, :parent_id1=>1)
608
+ changed_on = DB[:parents].get(:changed_on)
609
+ changed_on.wont_equal nil
610
+ changed_on.strftime('%F').must_equal Date.today.strftime('%F')
611
+ end
612
+ end
613
+
614
+ describe "PostgreSQL Array Foreign Key Trigger" do
615
+ before do
616
+ DB.create_table(:accounts){Integer :id, :primary_key=>true}
617
+ DB.create_table(:entries){Integer :id, :primary_key=>true; column :account_ids, 'integer[]'}
618
+ DB.pgt_foreign_key_array(:table=>:entries, :column=>:account_ids, :referenced_table=>:accounts, :referenced_column=>:id, :function_name=>:spgt_foreign_key_array, :referenced_function_name=>:spgt_referenced_foreign_key_array)
619
+ end
620
+
621
+ after do
622
+ DB.drop_table(:entries, :accounts)
623
+ DB.drop_function(:spgt_foreign_key_array)
624
+ DB.drop_function(:spgt_referenced_foreign_key_array)
625
+ end
626
+
627
+ it "should raise error for queries that violate referential integrity, and allow other queries" do
628
+ proc{DB[:entries].insert(:id=>10, :account_ids=>Sequel.pg_array([1]))}.must_raise Sequel::DatabaseError
629
+ DB[:entries].insert(:id=>10, :account_ids=>nil)
630
+ DB[:entries].update(:account_ids=>Sequel.pg_array([], :integer))
631
+ DB[:accounts].insert(:id=>1)
632
+ proc{DB[:entries].insert(:id=>10, :account_ids=>Sequel.pg_array([1, 1]))}.must_raise Sequel::DatabaseError
633
+ DB[:entries].update(:account_ids=>Sequel.pg_array([1]))
634
+ proc{DB[:entries].update(:account_ids=>Sequel.pg_array([2]))}.must_raise Sequel::DatabaseError
635
+ DB[:accounts].insert(:id=>2)
636
+ proc{DB[:entries].insert(:id=>10, :account_ids=>Sequel.pg_array([[1], [2]]))}.must_raise Sequel::DatabaseError
637
+ DB[:entries].update(:account_ids=>Sequel.pg_array([2]))
638
+ DB[:entries].all.must_equal [{:id=>10, :account_ids=>[2]}]
639
+ DB[:entries].update(:account_ids=>Sequel.pg_array([1, 2]))
640
+ DB[:entries].all.must_equal [{:id=>10, :account_ids=>[1, 2]}]
641
+ DB[:entries].update(:account_ids=>Sequel.pg_array([1]))
642
+ DB[:accounts].where(:id=>1).update(:id=>1)
643
+ DB[:accounts].where(:id=>2).update(:id=>3)
644
+ proc{DB[:accounts].where(:id=>1).update(:id=>2)}.must_raise Sequel::DatabaseError
645
+ proc{DB[:accounts].where(:id=>1).delete}.must_raise Sequel::DatabaseError
646
+ DB[:accounts].where(:id=>3).count.must_equal 1
647
+ DB[:accounts].where(:id=>3).delete
648
+ proc{DB[:accounts].delete}.must_raise Sequel::DatabaseError
649
+ DB[:entries].delete
650
+ DB[:accounts].delete
651
+ end
652
+ end
653
+
654
+ describe "PostgreSQL Force Defaults Trigger" do
655
+ before do
656
+ DB.create_table(:accounts){integer :id; integer :a, :default=>0; String :b; integer :c; integer :d, :default=>4}
657
+ DB.pgt_force_defaults(:accounts, {:a=>1, :b=>"'\\a", :c=>nil}, :function_name=>:spgt_force_defaults)
658
+ @ds = DB[:accounts]
659
+ end
660
+
661
+ after do
662
+ DB.drop_table(:accounts)
663
+ DB.drop_function(:spgt_force_defaults)
664
+ end
665
+
666
+ it "should override default values when inserting" do
667
+ @ds.insert
668
+ DB[:accounts].first.must_equal(:id=>nil, :a=>1, :b=>"'\\a", :c=>nil, :d=>4)
669
+
670
+ @ds.delete
671
+ @ds.insert(:id=>10, :a=>11, :b=>12, :c=>13, :d=>14)
672
+ DB[:accounts].first.must_equal(:id=>10, :a=>1, :b=>"'\\a", :c=>nil, :d=>14)
673
+ end
674
+ end
675
+
676
+
677
+ describe "PostgreSQL JSON Audit Logging" do
678
+ before do
679
+ DB.extension :pg_json
680
+ DB.create_table(:accounts){integer :id; integer :a}
681
+ DB.pgt_json_audit_log_setup(:table_audit_logs, :function_name=>:spgt_audit_log)
682
+ DB.pgt_json_audit_log(:accounts, :spgt_audit_log)
683
+ @ds = DB[:accounts]
684
+ @ds.insert(:id=>1)
685
+ @logs = DB[:table_audit_logs].reverse(:at)
686
+ end
687
+
688
+ after do
689
+ DB.drop_table(:accounts, :table_audit_logs)
690
+ DB.drop_function(:spgt_audit_log)
691
+ end
692
+
693
+ it "should store previous values in JSON format in audit table for updates and deletes on main table" do
694
+ @logs.first.must_be_nil
695
+
696
+ @ds.update(:id=>2, :a=>3)
697
+ @ds.all.must_equal [{:id=>2, :a=>3}]
698
+ h = @logs.first
699
+ h.delete(:at).to_i.must_be_close_to(10, DB.get(Sequel::CURRENT_TIMESTAMP).to_i)
700
+ h.delete(:user).must_be_kind_of(String)
701
+ txid1 = h.delete(:txid)
702
+ txid1.must_be_kind_of(Integer)
703
+ h.must_equal(:schema=>"public", :table=>"accounts", :action=>"UPDATE", :prior=>{"a"=>nil, "id"=>1})
704
+
705
+ @ds.delete
706
+ @ds.all.must_equal []
707
+ h = @logs.first
708
+ h.delete(:at).to_i.must_be_close_to(10, DB.get(Sequel::CURRENT_TIMESTAMP).to_i)
709
+ h.delete(:user).must_be_kind_of(String)
710
+ txid2 = h.delete(:txid)
711
+ txid2.must_be_kind_of(Integer)
712
+ txid2.must_be :>, txid1
713
+ h.must_equal(:schema=>"public", :table=>"accounts", :action=>"DELETE", :prior=>{"a"=>3, "id"=>2})
714
+ end
715
+ end if DB.server_version >= 90400