sequel_postgresql_triggers 1.4.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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